はじめての Vue CLIとVue Router
技術評論社から出ている『Vue.js入門 基礎から実践プリケーション開発まで』を手に入れた。
前回の競走成績ジェネレーターではRailsのプロジェクトの中でVue.jsを動かすという形で作ったけど、Vue.js主体でアプリケーションを作ることもできるのか?という疑問がありました。
読み進めていくと、やはりVue.jsにもVue CLIという開発環境構築をサポートしてくれるCLIツールがあるらしい。
ついでにVue RouterっていうVue.js公式のルーティングライブラリっていうものもあるらしい。 URL毎に特定のコンポーネントを出し分けることでルーティングを実現する感じ。
本読んでウンウン言ってても仕方ないのでとりあえず動かしてみました。
Vue CLIのインストール
Vue CLIをインストール
$ npm install -g vue-cli
ディレクトリ作って移動
$ mkdir vue-cli-sample $ cd vue-cli-sample
プロジェクト作成的なコマンド
$ vue init webpack .
いろいろな質問をされるのでエンターで進む。Vue Router使う?に対してはYesを選択
作成が完了すると以下の表示が出た。
To get started: npm run dev
ただサーバーを立ち上げる前にCloud9ではIPアドレスとホストの設定をする必要がある。
vue-cli-sampleプロジェクト内の/build/webpack.dev.conf.js
// these devServer options should be customized in /config/index.js devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, contentBase: false, // since we use CopyWebpackPlugin. compress: true, host: '0.0.0.0', port: '8080', disableHostCheck: true,
下の3行が書き換えた部分。
これで
npm start
動いた。
各ファイルと構成
/vue-cli-sample/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Vue CLI サンプル</title> </head> <body> <div id="app"></div> //ここ <!-- built files will be auto injected --> </body> </html>
application.html.erb的な立ち位置のファイル。<div id="app"></div>
部分にmain.jsから読み込んだ内容が挿入されることになる。
/vue-cli-sample/src/main.js
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
そのmain.jsがこれ。
#app
要素をマウントしたVueインスタンスが記述されている。
そのVueインスタンスにはimport router from './router'
で読み込んだルーティングファイルをrouterとして適用している。
そのルーティングファイルはというと、
/vue-cli-sample/src/router/index.js
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld } ] })
import Router from 'vue-router'
でvue routerを読み込んでいるのでnew Routerがルーターコンストラクタ。
import HelloWorld from '@/components/HelloWorld'
の部分で次に記載するHelloWorldコンポーネントを読み込んでルートパスに対して割当を行っている。
ではHelloWorldコンポーネントはというと、
/vue-cli-sample/src/components/HelloWorld.vue
<template> <div class="hello"> <h1>{{ msg }}</h1> <h2>Essential Links</h2> <ul> <li> //省略 リンクの内容です </li> </ul> <h2>Ecosystem</h2> <ul> //ここも省略 </ul> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h1, h2 { font-weight: normal; } //その他css </style>
この部分がルーティングによって表示される。
そしてこの形式が「単一ファイルコンポーネント」ってやつだ。
テンプレートとCSSとJSの3つを1つのコンポーネントとしてまとめることで、言語の役割とは別の粒度で切り出すことができる。
該当部分のhtml/css/JSを1ファイルで管理できるのは確かに見やすい……のかな?
では、ルーティングしたコンポーネント達は実際にどうやって反映されているのか。
/vue-cli-sample/src/App.vue
<template> <div id="app"> <img src="./assets/logo.png"> <router-view></router-view> //この部分にレンダリングされる </div> </template> <script> export default { name: 'App' } </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>
このファイルも単一ファイルコンポーネントだ。
<router-view>部分に各コンポーネント(ルーティングしたページ)がレンダリングされることを考えると、このApp.vueはコンポーネントファイルの親玉らしい。
/vue-cli-sample/src/main.jsを再び見てみると、
import Vue from 'vue' import App from './App' //ここ import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, //ここ template: '<App/>' })
最終的にindex.htmlに読み込まれるmain.js
その中でコンポーネントとして呼ばれているのがApp.vue
さらにその中で各コンポーネント(ルーティングされたページ)が呼び出される
という構成になっていることがわかる。
せっかくなのでページを追加してみる。
index.jsに追記
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import UserList from '@/components/UserList' //コンポーネントを読み込む Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { //新しいページ path: '/users', name: 'UserList', component: UserList } ] })
componentsディレクトリ内にUserList.vueを新規作成し以下を記述
<template> <div class="list"> <h2>ユーザー一覧ページ</h2> <p>{{ msg }}</p> <router-link to="/">ホームへ</router-link> </div> </template> <script> export default { name: 'UserList', data(){ return { msg: '頑張ってユーザ一覧を表示します' } } } </script> <style scoped> h1, h2 { font-weight: normal; color: red; } </style>
ここで一旦コンポーネント自体の話。
.vueファイルのtemplate内は全体を
<div></div>
で囲む必要があるので注意が必要。コンポーネント毎にデータプロパティを持たせたい場合は上記のようにオブジェクトとしてではなく関数として書く必要があった気がする。
さて、HelloWorld.vueにもリンクを追加して実際に確認してみましょう。
シングルページアプリケーションができた。
感動したのが、たとえばUserList.vueの「ユーザ一覧ページ」という文章を書き換えてエディタ上で保存するだけで、ブラウザの方はF5更新することなく表示が書き換わる!
これはVue.jsのリアクティブシステムにおいて、コンポーネント毎に存在するウォッチャがコンポーネント内のデータをすべてリアクティブプロパティとして監視しているから実現している動きだそうです。
コンポーネントの内容を書き換えるとセッターを通じて変更通知が行って、ウォッチャのゲッターがデータを再取得することでコンポーネントが再度レンダリングされているんですね。
しかもこのリアクティブシステム、算出プロパティにおいては一度取得した算出結果をウォッチャにキャッシュして、それ以降算出プロパティが参照されたときは変更がない限りはキャッシュを返すことで計算コストを抑えているらしい。すごいなあ。俺には半ラーと半チャーのセットをオーダーしてキャッシュで支払うくらいしかできないよ。
感想
たしかに簡単にSPAが作れた。
最初は「Vueでルーティング?サーバーサイドと組み合わせるときはどうするの?」と思ったけど、Vueの各コンポーネントをルーティングするということなので例えばRailsのコントローラ#アクションで表示したページ内でVue Routerを使うことは有効、たぶん。
Vue CLIも簡単で便利だけど、サーバサイドをAPIのみに専念するにしてもこのVue CLIで作ったVueプロジェクトの「中で」Railsを動かすこととかあるのか?という疑問が浮かぶ。
RailsとVue.jsの組み合わせを調べると大抵はRailsプロジェクトの中でVueを動かす形になっている。
この辺は実際の使用目的と設計によるんだろうけど、Rails + Vue.jsの組み合わせ方の実例を知りたいなあ。GitHubでそういうの見れたりすんのかな。
参考リンク
Vue-routerを使って、SPAをシンプルにはじめてみる - Qiita