初めてのVue.js「入門編」基礎から学ぼう!サンプルコードで丁寧に解説(郵便番号から住所の自動取得をやってみる)
Vue.jsとは
Vue.jsは、ユーザーインターフェースを開発するための、Javascriptフレームワークです。LINE、 DeNA、ZOZOといった有名企業でも導入され、人気のフレームワークのひとつとなっています。今回は実際に使ってみながらVue.jsの使い方をご紹介していきます。
UIのためのJavascriptフレームワークには、ほかにもReact、Angularといった有名なものがあります。次の図は日本のGoogleトレンドです。赤色のReactが長らく優勢でしたが、2018年の後半あたりから青色のVue.jsが追い抜いているのがわかります。
Vue.js人気の理由はとにかく手軽なところ。ページの一部をちょっと便利にしたい、といったときにも威力を発揮します。公式チュートリアルの日本語訳や、有志による解説も豊富です。フレームワークは初めて、jQueryなら触ったことがあるけど……という方にはぜひおすすめです。
Vue.jsの特徴
Vue.jsが担うのは、下の図のような領域です。Vue.jsはMVVM(Model View View-Model)と呼ばれるデザインパターンをとっています。
下の図では、左端のViewはDOM要素で、ユーザーから直接見える部分に相当します。右端のModelは、データや振る舞いを記述するJavascriptの内容としておきましょう。VueはViewModelとして、ModelとViewの間に入って、複雑な操作を代わりに受け持つ、というイメージです。
一言で言えば、複雑なコードを直接書かなくても、ページの中の任せたい要素や、データの内容、振る舞いといったもの指定するだけで、さまざまな動作やデータの監視をVueにお任せできる、というわけです。
ここでVueを使う強みは、「双方向バインディング」を爽快に実現しているところ。簡単に言えば、ユーザーがデータを入力すれば中のデータも変わり、中のデータが変わればユーザーに表示されるものも連動して変わる、という仕組みです。わざわざ更新させるコードを書かなくても、まるっとお任せできるのです。
React、Angularと違うところ・似ているところ
同種のフレームワークであるReact、Angularと、もう少し詳しく比較してみましょう。
Vue、React、Angularの違うところ
Vue | React | Angular | |
---|---|---|---|
記法 | Vueテンプレート:HTML、CSS、JSONを中心とした書き方 | VSX:JavascirptとXMLが混在する独特の書き方 | Typescript:型指定などを厳密にしたJavascriptの発展形 |
リリース | 2014 | 2013 | 2010 |
開発の中心 | Evan You氏 | ||
自由度 | ほどほどの柔軟さと厳格さ、大規模開発は計画的に行なう必要あり | もっとも柔軟だが、ガイドラインと秩序立ては厳密 | もっとも厳格に構造が決まっている、それに従えば作業効率は高い |
拡張機能 | 主要ライブラリは公式がサポート | 多数のライブラリが乱立 | 多数のライブラリが乱立 |
Vue、React、Angularの似ているところ
- 仮想DOMの活用
- 分割・再利用可能なコンポーネント
- リアクティブシステム
- 軽量なコアライブラリ
まとめ
Vueは後発なので、他のフレームワークのいいところを積極的に取り入れています。厳しくルールを決めすぎない方針があり、小〜中規模開発には向いています。そのぶん大規模開発では注意が必要です。
基本:Vueを使ってみよう
さて、そろそろ実践にうつりましょう。Vueを導入するには、HTMLに下記のタグを追加するだけです。
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
読み込み完了! ここからは、郵便番号を入力すると、それにマッチする住所が表示される、という仕組みを作ってみます。まずは完成形をお見せします。
See the Pen
Vue & axios : zipcode to address Demo 2 by webkikaku (@webkikaku)
on CodePen.
テキスト入力を即時反映させてみる
では最初に、郵便番号の入力を受け付ける部分を作ってみましょう。HTMLファイルを新たに作成し、bodyの中に、入力用のinput要素、結果表示用のdiv要素、それからタイトルのp要素を用意してみます。
<div id="demo"> <p class="title">zipcode to address demo</p> <input> <div class="result"> 結果をここに表示 </div> </div>
次に、JavascriptでVueインスタンスを作成してみます。
const vm = new Vue ({ el: '#demo', data: { zip:'0000000' } });
このように、Javascriptのオブジェクトの形で、データやふるまいなどを指定していくのがVueの基本的な使い方です。「vm」はViewModelの略で、Vueでよく使われる定数名のようです。中に格納されている、オプションと呼ばれる要素をひとつずつ紹介します。
elはエレメントの略で、HTMLのこの部分をVueにお任せしますよ、という宣言です。今回はdiv id=”demo”を任せたいので、「’#demo’」となっています(CSSのセレクタと同じですね)。指定できるのは必ず1つの要素になります。
dataは、入力する郵便番号データや、出力する住所といった、様々なデータを格納できます。初期値を入力することもできますよ。
Vueを紐づける
いよいよHTMLとVueを連携させてみましょう。すでにelオプションによって紐付けはできているのですが、Vueに「このデータを見てね」「クリックしたらこの動作をしてね」といった指示を何も伝えていない状態です。
この指示をおこなう要素をVueではディレクティブと呼び、HTMLに直接書きます。たいていが「v-」で始まるので見分けやすいと思います。inputの入力をそのまま表示するために、次のように追記してみましょう。
<div id="demo"> <p class="title">zipcode to address demo</p> <input v-model="zip"> <div class="result"> 入力:{{zip}} <br> </div> </div>
v-modelというのは、入力ボックスやチェックボックス、セレクトボタンなどに使うディレクティブで、今回ならば、この入力ボックスの中身をVueのdataの中にある「zip」と対応させますよ、という意味になります。
2重の波括弧で「zip」と書かれているのは、ここに「zip」というデータを表示させてね、という指示になります。この2重波括弧をVueではムスタッシュ記法と呼んでいます。Vueはデータの変更を即時に反映してくれるので、入力ボックスの中身が変わると、出力される文字も連動して変わります。以下のデモで試してみてください。
See the Pen
Vue & axios : zipcode to address Demo 1 by webkikaku (@webkikaku)
on CodePen.
filterでデータを変形させてみる
さて、先ほどのデモにすでにあったのですが、今回扱うのは郵便番号ですので、必ず7ケタの数字になってほしいですよね(ハイフンは今回扱いません)。filtersオプションで表示形式を指定してみましょう。HTMLとJavascriptに次のように追記します。
<div id="demo"> <p class="title">zipcode to address demo</p> <input v-model="zip"> <div class="result"> 入力:{{zip}} <br> フィルタ:{{zip | filterZip}} </div> </div>
const vm = new Vue ({ el: '#demo', data: { zip:'' }, filters: { filterZip: function(d){ let buf = (d+'0000000').slice(0,7); return isNaN(buf) ? '半角数字で入力してください' : buf.toString() } } });
HTMLの6行目、二重の波括弧のあとに縦棒があり、「filterZip」と書かれています。これは、zipというデータに、filterZipというフィルターをかけて出力しますよ、という意味になっています。
filterZipの内容は、(1)入力を7文字の文字列で表示、(2)半角数字以外が入力されるとアラートを出す、というものです。データを文字列で扱っているのは、0から始まる郵便番号のときに、5ケタや6ケタになってしまわないためです。
computedでデータを操作してみる
さて、filtersはあくまでも表示用にデータを整える役割を持っています。データそのものを使って複雑な計算を行ない、新しいデータを作るには、computedオプションが適しています。普通のJavasript関数で、結果をreturnしてあげればOKです。
const vm = new Vue ({ el: '#zip', data: { inputZip:'', defaultZip:'1000000' }, computed: { computedZip: function(){ return !isNaN(this.inputZip) && this.inputZip.length == 7 ? this.inputZip : this.defaultZip } } })
データはinputZip、defaultZipの2つです。computedの中にいるcomputedZipには、7桁の半角数字、という条件を満たしている時だけ、inputZipの値が入ります。それ以外ではdefaultZipの内容である「1000000」が入ります。なお、同じVueインスタンスの中のデータを呼ぶときは、頭に「this」をつけて、「this.inputZip」と書きます。
<div id="data"> <p class="title">zipcode to address demo</p> <input v-model="inputZip" v-bind:placeholder="defaultZip" maxlength="7"> <div class="result"> {{computedZip}} </div> </div>
HTMLはこのようになりました。v-bindディレクティブは、もともとあるHTML要素の属性をVueと紐付けますよ、という指示です。placeholder属性はinput初期表示を設定するものですが、その中身をVueに任せて、defaultZipの値を入れている、ということになります。
methodでAPIを叩いてみる
いよいよ、APIを叩いて住所情報を取得してみましょう。先ほど作ったcomputedZipの値を使って通信してみます。APIを叩く、といった動作を実行するにはmethodsオプションを使います。こちらもごく普通の関数を格納することができます。
住所検索にはこちらのフリーAPIを使わせていただきます。パラメータ「?zipcode=xxxxxxx」で郵便番号を受け付けて、住所データを返してくれるものです。「getAddress」メソッドを追加したJavascriptは下記のようになりました。
const URL_API = "https://api.zipaddress.net/"; const vm = new Vue ({ el: '#zip', data: { inputZip:'', defaultZip:'1000000', results:'' }, computed: { computedZip: function(){ return !isNaN(this.inputZip) && this.inputZip.length == 7 ? this.inputZip : this.defaultZip } }, methods: { getAddress: function(z){ let params = {params:{zipcode: z}}; axios .get(URL_API, params) .then(res => { this.results = res.data.code == 200 ? res.data.data.fullAddress : res.data.message; }); } } })
少し長くなってしまいますが、解説していきますよ。
APIとの通信には、JavascriptのHTTPクライアントライブラリ「axios」を使っています。導入は、Vueの導入時と同じように、こちらのソースをHTMLの中に貼り付ければOKです。
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
axiosの使い方も簡単に紹介しておきます。上記の19行目のように、axios.get(URL)とすればGETリクエストを送信できます。第2引数はパラメータを渡すためのもので、今回は郵便番号をここで設定しています。通信自体はなくても可能ですが、複数のパラメータを扱うときにはとても便利な書き方ですよ。
axiosはpromiseを返します、20行目のようにメソッドチェーンを続けて、処理を.then()の中に書きます。今回は使いませんが、この後に続けて、.catch()で通信エラー時の処理、.finally()で成功・失敗時共通の最終処理を書くこともできますよ。
今回then()の中では、結果の住所をVueが持っているデータのひとつであるresultsに格納しています。送ったのがもし無効な郵便番号なら、無効でした、というメッセージをAPIが返してくれるので、そのままresultsに格納しています。
<div id="demo"> <p class="title">zipcode to address demo</p> <input v-model="inputZip" :placeholder="computedZip" v-on:keypress.enter="getAddress(computedZip)" maxlength="7"> <button @click="getAddress(computedZip)">boom</button> <div class="result"> {{results}} </div> </div>
HTMLはこのようになりました。「getAddress」は、ボタンをクリックしたときか、入力欄でエンターキーを押したときに実行されます。こうしたイベントの処理はv-onディレクティブで行ない、「v-on:keypress.enter」と書きます。
@clickはv-on:clickの短縮形です。さきほどの3行目を@keypress.enterと書いても意味は同じになります。また3行めの:placeholderは、v-bind:placeholderの短縮形となっています。これで、郵便番号を元に住所が検索できるはずです!
トランジションの実装
Vueはトランジションもサポートしています。結果の住所が変わったときに、ちょっとした動作を追加してみましょう。簡単な形では、Javascriptを使うことなく、HTMLとCSSを追記するだけでOKですよ。「div class=”result”」の中をこのようにしてみます。
<div class="result"> <transition name="trans"> <p class="res" :key="results">{{results}}</p> </transition> </div>
「transition」タグはHTMLではなく、Vue独自のものです。後ほど説明しますが、Vueではオリジナルの要素を「コンポーネント」として、HTMLタグのように使いまわすことができます。transitionはVueにもともと入っている「組み込みコンポーネント」です。
transitionは、内側の要素が追加・削除されたときに、指定したアニメーションを実行してくれます。読み取られるのは「要素の変更」であるという点は要注意です。テキストの変更は読んでくれないので、結果をpタグで囲い、「:key=”results”」と指定しています。
こうすることで、Vueにとっては、resultsの内容が変われば、transitionのなかの「古いkeyのp要素が消え」、「新しいkeyのp要素が追加された」ことになるということです。アニメーションの内容はCSSで指定します。
.result { position: relative; margin: 10px auto; } .result .res { position: absolute; left: 50%; top: 0; transform: translateX(-50%); } .trans-enter-active, .trans-leave-active { transition: all .5s; } .trans-enter-to, .trans-leave { opacity: 1; } .trans-enter{ opacity: 0; transform: translate(-50%, 10px) !important; } .trans-leave-to { opacity: 0; transform: translate(-50%, -10px) !important; }
12行目から先で、transという名前のtransitionの内容を記述しています。この名前は自由に決められますよ。新しい要素は「.trans-enter」から「.trans-enter-to」、古い要素は「.trans-leave」から「.trans-leave-to」の状態に変化します。変化時間は「.trans-enter-active, .trans-leave-active」で指定しています。
ちなみに、2つのp要素が同時に出入りすることになるため、位置がおかしくなってアニメーションが思い通りに行かない場合があります。それを避けるために、10行目以前で「position: absolute」を有効にしています。
発展:コンポーネントの分割
これで郵便番号からの住所検索はできました。ですが、こういった機能は単体ではなく、たとえば会員登録フォームなどで使うものですよね。そこには名前、電話番号、生年月日、メールアドレス、希望プランといった複数の入力欄があるかもしれません。
さらには会員登録フォームも、あるウェブアプリの1ページで、そのほかの会員メニュー、特典コンテンツ、新着記事などもまとめて、SPA(Single Page Application)として構成したい、という場合もあります。
それ全部Vueでできます! 導入のみになりますが、より実践的な環境構築と、コンポーネントの分割についてお伝えしていきます。
Vue CLIでプロジェクトを作成してみる
今回は、Vue CLI3系から導入されたVue CLI UIを使用してみます。Node.jsをインストールしていない方はこちらから。ダウンロードして解凍するだけですよ。
それでは、ターミナルで以下のコードを実行します。
npm install -g @vue/cli vue ui
ブラウザでVue CLI UIの画面が開いたら成功です。まだ何もありませんが、プロジェクトマネージャで「作成」タブを選び、お好きなディレクトリを指定して、「ここに新しいプロジェクトを作成する」に進みます。
プロジェクト名は好きなものにしてみてください。内容は特に変更しなくてもOK。babel+webpackやnpm/yarn、Gitなどの知識があるとさらに効率的な開発ができます。本記事では割愛しますが、ぜひ習得してみてください。
さて、生成が終了するとこんな画面が開くはずです。左のタブから一番下の「タスク」を選択、そこからserveを選び、「タスクの実行」ボタンをクリックします。実行が終了したら、「アプリを開く」で最初の画面を確認することができます。
以下が最初の画面になります。ローカルホストの8080番ポートで、新しいプロジェクトのトップページが開きます。こちらはデフォルトで設定されているものになります。これから、今回自動生成されたファイルを見ていきます。
VueCLIで作成されたファイルの中身
開発は、プロジェクトを新規作成する際に指定したフォルダ内で行ないます。いろいろなファイルがありますが、以下の4つが主に編集にかかわる内容となります。
- public/index.html
- src/main.js
- src/app.vue
- src/components/HelloWorld.vue
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title>xxx</title> </head> <body> <noscript> <strong>We're sorry but xxx doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
14行目がVueとやりとりしているdiv要素です。headなどは自由に編集してもOKです。さきほどserveしたものをdevtoolでみると、18行目にJavascriptの参照が追加されています。開発時に分けていたJSやVueファイルを1ファイルにビルドして、自動で参照を追加してくれる、というわけですね。
<script type="text/javascript" src="/app.js"></script>
main.js
import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
こちらがメインのVueインスタンスです。いじる必要は基本ありません! importでVue本体と、App.vueを読み込んで、render関数というもので「div id=”app”」に書き出していることが確認できればOKです。
App.vue
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'app', components: { HelloWorld } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
こちらがApp.vueファイルです。1ファイルが1つのVue要素=コンポーネントになっており、単一ファイルコンポーネントと呼ばれています。開発や管理がしやすく、使い回しにも便利なため、とくに中規模の開発では必須となってきますよ。
1つのファイルは、template、script、styleの3部構成になっていて、それぞれHTML、Javascript、CSSに対応しています。基本的な書き方はほとんど変わりません。4行目にHelloWorldというユーザー指定のタグがあります。9行目でimportしているものになります。
このように、別のファイル(HelloWorld.vue)で作ったコンポーネントを、より広い範囲をカバーするコンポーネント(App.vue)の中で使って階層構造を作る、といったことも可能になっています。複数を並べたり、使いまわしたりすることも簡単です。初期状態では下図のようなイメージです(本当はvueとjsを1つにまとめた新しいjsファイルが生成されます、あくまでイメージです)。
HelloWorld.vueの中身は、先ほどローカルホストで立ち上げたページのほぼ全要素がまとまったものです。タイトルの「Welcome to Your Vue.js App」は、親要素であるApp.vue内にタグを記述するときに指定された可変データだというところもポイントです。ソースは長いので割愛しますが、ぜひ動作を確かめてみてくださいね。
さらに深めるためにはこちら
ライブラリも充実
Vueの魅力の一つは、強力なライブラリが公式でサポートされていることです。コンポーネントの入れ子構造が複雑になると、横断してステータスを一元管理したくなってきます。Vuexはそんなときに活躍する公式拡張機能です。
ほかにもSPA(Single Page Application)などに使えるルーティングライブラリVue-Routerが公式サポートされているほか、ローディング画面を実装するvue-loading、カレンダーや日付ピッカーとして活躍するv-calendarなど、有志による多数のライブラリがあります。
nuxt.js、Laravel
nuxt.jsは、Vueを用いてwebアプリを開発するためのフレームワークです。Vue本体のほか、Vuex、Vue-Router、Vue Server Renderer、Vue-Metaなどがパッケージされています。大きめのSPAやwebアプリをVueで作りたいときは効率を上げてくれるでしょう。
LaravelはPHPフレームワークですが、バージョン5.3からVueが標準搭載されるようになりました。バックエンドでwebAPIをLaravelでり、フロントエンドでSPAをVueで実装する、といった連携がさらに容易になっています。
Vue.js devtools
最後にデバックツールをご紹介します。Chromeの拡張機能である「Vue.js devtools」は、ページに使われているVueの状態を読み込んで開発を助けてくれるもの。Vueでの開発では必ず役に立ちますよ。
活用の幅が広がるVue.js
以上、Vue.jsの基本要素から、実際の開発で使えるツールまでをご紹介してきました。Vueテンプレートは、他のJavascriptフレームワークに比べて学習コストが低く、手軽に使えるため、ちょっとした用途にもおすすめです。
特にデータの双方向のやりとりは、jQueryなどでは一つ一つコードを書いてDOMを更新してあげなければいけませんが、Vueではほとんどをお任せできるので、この点だけでも使う価値があると言えるかと思います。
もちろん大きなSPAなどにも活用できるVue。LaravelやWordpressなどとの合わせ技も可能です。入り口は手軽ですが、高度な実装にはそれなりの知識が必要になるのも事実です。人気なだけに、ドキュメントやコミュニティも充実していますので、ぜひ本記事をきっかけに深めてみてくださいね。