こんにちは、平奥です!
最近のフロントエンド開発では、シングルページアプリケーション( SPA )が主流になってきており、それを実現するために JavaScript のフレームワークが欠かせないものとなってきました。
React、AngularJS、Angular、Vue.js、Backbone.js、Riot.js、Aurelia.js、Ember.js...
いろんなフレームワークがここ数年で世の中に出てきました。それぞれのフレームワークで、特徴や他のフレームワークより優れている機能、学習コスト、メンテナンス性、開発効率が違ってきます。
そこで今回は最近勢いのある Vue.js で簡単なアプリケーションを作ってみて、今使っているフレームワークとどう違うのか比較してみようと思いました。それでは早速いってみましょう!
準備
まず開発環境を整えます。vue のインストールから始まり、今回はお手軽に Vue.js を始められるように vue-cli を使用します。
また Bootstrap も使えるようにしておきます。
Vue.js のインストール
1 |
$ npm install vue |
vue-cli をインストールして、vue-sample プロジェクトを作成
1 2 3 4 5 6 7 8 |
# vue-cli をインストール $ npm install --global vue-cli # "webpack" ボイラープレートを使用した新しいプロジェクトを作成する $ vue init webpack vue-sample # 依存関係をインストールしてgo! $ cd vue-sample $ npm install $ npm run dev |
Bootstrap と Bootstrap-vue のインストール
1 2 |
npm install bootstrap@4.0.0-alpha.6 npm install --save bootstrap-vue |
プロジェクト作成時に以下の選択が表示されますが、すべて 'Y' を入力しても大丈夫です。
今回は vue-router も使いたいので Install vue-router? の箇所では 'Y' を入力してください。
以上で準備は完了です。
以下のようなディレクトリ構成でプロジェクトが作成されます。
これを基にしてアプリケーションを作成していきます。
アプリケーションを作るにあたって
公式サイトに情報が豊富に載せられており、また日本語にも翻訳されています。アプリケーションを作成するには公式サイトを参考にして作成するのがよいかと思います。
今回はフォーム画面とリスト画面を作成し、フォーム画面で登録した内容をリストに表示する簡単なアプリケーションを作成してみようと考えています。
画面は router を使って切り替えるようにできるようにします。
アプリケーション作成
単一ファイルコンポーネントで画面のコンポーネントを作成
フォーム画面とリスト画面は単一ファイルコンポーネントで作成します。それぞれ Add.vue と List.vue という名前にします。
普通のコンポーネントと単一ファイルコンポーネントは、同じコンポーネントとなりますが、単一ファイルコンポーネントは HTML、JS、CSSをひとまとめにできたり、グローバルを汚染しない等のメリットがあります。HTML は "template" タグの中に記述し、JS は "script" 、CSS は "style" タグの中に記載します。
詳しくは、公式ページの 単一ファイルコンポーネント を参照してください。
components フォルダに Add.vue と List.vue ファイルを追加
フォーム画面の作成
Add.vue ファイルを使ってフォーム画面を作成します。
"template" は以下となります。
1 2 3 4 5 6 7 8 9 10 11 12 |
<template> <div id="app"> <form> <h3>■ データ登録</h3> <input-name></input-name> <input-age></input-age> <input-mail></input-mail> <br> <input-button></input-button> </form> </div> </template> |
"input-name"、"input-age"、"input-mail"、"input-button" はカスタムコンポーネントで独自で定義したコンポーネントとなります。以下の "script" タグのところで詳しく説明します。
"script" は以下となります。カスタムタグを定義して、コンポーネントを登録しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<script> import Vue from 'vue' export default { } import PropertyStore from '../models/store.js' import Bus from '../models/bus.js' var InputName = Vue.extend({ name: 'input_name', template: '<div class="form-gxroup"><label for="Name">Name</label><input type="text" v-model="sharedState.state.property.name" class="form-control" id="Name"></div>', data: function () { return { privateState: {}, sharedState: PropertyStore } } }) // コンポーネントを登録 Vue.component('input-name', InputName) var InputAge = Vue.extend({ name: 'input_age', template: '<div class="form-gxroup"><label for="Age">Age</label><input type="text" v-model="sharedState.state.property.age" class="form-control" id="Age"></div>', data: function () { return { privateState: {}, sharedState: PropertyStore } } }) // コンポーネントを登録 Vue.component('input-age', InputAge) var InputMail = Vue.extend({ name: 'input_mail', template: '<div class="form-gxroup"><label for="Mail">Mail Address</label><input type="text" v-model="sharedState.state.property.mail" class="form-control" id="Mail"></div>', data: function () { return { privateState: {}, sharedState: PropertyStore } } }) // コンポーネントを登録 Vue.component('input-mail', InputMail) // ボタンのコンポーネントを作成 var InputButton = Vue.extend({ name: 'input-button', template: '<button class="btn btn-primary" v-on:click="save">Regist</button>', data: function () { return { privateState: {}, sharedState: PropertyStore } }, methods: { save: function (event) { console.log(PropertyStore.state.property.name) console.log(PropertyStore.state.property.age) console.log(PropertyStore.state.property.mail) // emit Bus.$emit('store-update', 'Add.vue') // リダイレクト this.$router.push('/List') } } }) // コンポーネントを登録 Vue.component('input-button', InputButton) </script> |
ここでは大きく分けて、テキスト入力のコンポーネントとボタンのコンポーネントを作成しています。それぞれ説明していきます。
テキスト入力のコンポーネント
コンポーネントは Vue.extend で作成します。このコンポーネントでは name、template、data を使用しています。
それぞれ名前を定義、コンポーネントの HTML を定義、コンポーネントで使用するデータを定義しています。
ここでは v-model を使ってテキストボックスに入力された情報を data の PropertyStore に保存しています。
ボタンコンポーネント
ボタンコンポーネントでは name、template、data、methods を使用しています。
name、template、data はテキスト入力のコンポーネントで説明したので、methods を説明します。
methods ではコンポーネントに組み込まれるメソッドを定義します。
ここでは ボタンが押された時のメソッドである save メソッドを定義しています。
リスト画面の作成
List.vue ファイルを使ってリスト画面を作成します。
"template" は以下となります。
1 2 3 4 5 6 7 |
<template> <div id="app"> <h3>■ 登録データ一覧</h3> <grid-component></grid-component> </div> </template> |
ここではリストを表示するための grid-component が定義されています。
grid-component は以下に説明する "script" に定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<script> // インポート import Vue from 'vue' import Bus from '../models/bus.js' import {Grid} from '../grid/grid.js' import PropertyStore from '../models/store.js' export default { name: 'List' } var receiveData = [] Vue.component('grid-component', { template: '<demo-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>', data: function () { return { searchQuery: '', gridColumns: ['name', 'age', 'mailAddress'], // gridData: [{name: PropertyStore.state.property.name, age: PropertyStore.state.property.age, mailAddress: PropertyStore.state.property.mail}] gridData: receiveData } } }) // メッセージ受取 Bus.$on('store-update', (vue) => { console.log(vue) receiveData.push({name: PropertyStore.state.property.name, age: PropertyStore.state.property.age, mailAddress: PropertyStore.state.property.mail}) }) // コンポーネントを登録 Vue.component('grid', Grid) </script> |
基本的には、やっていることはコンポーネントの定義と登録であり、フォーム画面とさほど変わりません。
ただし、コンポーネントを import を使って取り込んでいたり、メッセージ受取用の処理が実装されています。
こちらも "styles" に関しては重要ではないので説明は割愛させていただきます。
コンポーネント間のデータの受け渡しについて
コンポーネント間のデータの受け渡しはイベントを使う方法と共通のオブジェクトを使って受け渡す方法を試してみたかったので両方とも実装しました。
まず、共通のオブジェクトを使って受け渡す方法ですが、新たに models ディレクトリを作成して、その中に共通のオブジェクトを定義した store.js ファイルを作成しました。
store.js ファイルの実装は以下となります。
1 2 3 4 5 6 7 8 9 10 11 |
var PropertyStore = { debug: true, state: { property: { name: '', age: '', mail: '' } } } export default PropertyStore |
共通で受け渡したいデータを保持する変数を定義しています。
この PropertyStore を使ってデータを受け渡しています。
データを送る側の Add.vue の実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 共通モジュールをインポート import PropertyStore from '../models/store.js' // data を使って共通モジュールにデータを設定 var InputName = Vue.extend({ name: 'input_name', template: '<div class="form-gxroup"><label for="Name">Name</label><input type="text" v-model="sharedState.state.property.name" class="form-control" id="Name"></div>', data: function () { return { privateState: {}, sharedState: PropertyStore } } }) |
受け取る側の List.vue の実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 共通モジュールをインポート import PropertyStore from '../models/store.js' Vue.component('grid-component', { template: '<demo-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>', data: function () { return { searchQuery: '', gridColumns: ['name', 'age', 'mailAddress'], gridData: [{name: PropertyStore.state.property.name, age: PropertyStore.state.property.age, mailAddress: PropertyStore.state.property.mail}] // gridData: receiveData } } }) |
こちらの受け取り方では共通モジュールの構造上1件のデータしか登録できません。イベントで使う共通オブジェクトと一緒のオブジェクトを使いたかったので、このような仕様にしました。
次にイベントを使ってデータを受け渡す方法です。
データを送信する側の Add.js で $emit を使ってイベントを発火しています。ちなみに $emit の第2パラメータに指定したデータを送信できるのですが、
今回は共通オブジェクトを参照させるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// ボタンのコンポーネントを作成 var InputButton = Vue.extend({ name: 'input-button', template: '<button class="btn btn-primary" v-on:click="save">Regist</button>', data: function () { return { privateState: {}, sharedState: PropertyStore } }, methods: { save: function (event) { console.log(PropertyStore.state.property.name) console.log(PropertyStore.state.property.age) console.log(PropertyStore.state.property.mail) // emit Bus.$emit('store-update', 'Add.vue') // リダイレクト this.$router.push('/List') } } }) |
受け取り側の List.vue では $on を使ってイベントを受け取っています。受け取ったデータは data を使ってコンポーネントに渡してリスト表示しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var receiveData = [] Vue.component('grid-component', { template: '<demo-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>', data: function () { return { searchQuery: '', gridColumns: ['name', 'age', 'mailAddress'], // gridData: [{name: PropertyStore.state.property.name, age: PropertyStore.state.property.age, mailAddress: PropertyStore.state.property.mail}] gridData: receiveData } } }) // メッセージ受取 Bus.$on('store-update', (vue) => { console.log(vue) receiveData.push({name: PropertyStore.state.property.name, age: PropertyStore.state.property.age, mailAddress: PropertyStore.state.property.mail}) }) |
まとめ
今回はざっくり Vue.js がどういうものなのかということをまずは触ってみようということで、サンプルアプリを作成しました。
Vue.js の特徴である MVVM の設計パターンには触れていないので、この設計パターンを使って実装すればもっとデータが扱いやすくなるのかなと思いました。
また redux を実現する vuex などもありますので今後使ってみようと思います。
いろんなフレームワークに触れて、そのフレームワークの思想や考え方に触れてみるのもよい機会だと感じました。