Vue.jsはそのまま使ってもよいですが「自分(プロジェクト)に合ったフレームワーク」を見つけるのにも向いています。
これは、後発フレームワークだけあり各フレームワークの特徴を意識した設計がなされているためです。他の著名なフレームワークとの特徴を比較した文書もあるので、こちらをチェックしながら導入を検討するとよいと思います。
そのため、以下はVue.jsの紹介と他フレームワーク(Knockout.js と Angular)へのステップという2セクションに分けて紹介していきたいと思います。
JavaScriptフレームワークの導入を行いたいがこの選択は慎重にいきたい、という状況であれば最初にVue.jsを試金石としてみて、効果的と感じられる機能からAngularやKnockout.jsに流れていくというのは十分ありだと思います。
- 2016/06: Vue.jsの1.x系に合わせて記述・サンプルをアップデートしました
- 2017/06: Vue.jsの2.x系に合わせて記述・サンプルをアップデートしました
3Stepで学ぶVue.js
1st Step
まずはシンプルな例。以下のJSFiddleで動かしながら確認できます。
HTML
<div id="todoApp">
<h2 v-bind:style="fontStyle">{{ title }}</h2>
<input type="text" v-model="todo" v-on:keyup.enter="addTodo">
<div>
<div v-for="t in todos" v-bind:key="t.id"
v-bind:class="['todo-default', t.doing ? 'todo-doing' : '']"
v-on:click="begin(t)">
{{t.name}}
</div>
</div>
</div>
CSS
.todo-default {
padding: 5px;
border-bottom: 1px solid silver;
}
.todo-doing {
background-color: floralwhite;
}
JavaScript
var app = new Vue({
el: '#todoApp',
data: {
title: "Welcome to Vue.js",
fontStyle: {
fontSize: "20px",
},
todo: "",
todos: []
},
methods: {
addTodo: function() {
this.todos.push({
id: this.todos.length,
name: this.todo,
doing: false
})
this.todo = ""
},
begin: function(todo){
todo.doing = !todo.doing;
}
}
})
Vue.jsはVueクラスのインスタンスを作成するところからすべてが始まります。
この構成要素として、もっとも重要なものは以下3点になります。
-
el
:対象となるHTML要素。ここで指定した要素に対し、data
で指定した値がバインドされる。 -
data
:バインドする値 -
methods
:イベントなどから呼び出す各種処理
つまりel
で指定した要素がVue.jsの管理下となり、この配下の要素についてv-bind
/v-model
で属性、v-on
でイベントの設定を行うことでdata
の値を表示させたり、methods
で定義した処理を呼び出すことができます。なお、v-bind
は:
、v-on
は@
の省略記法が可能です。
具体例を示すため、上記のコードを使いながら説明をしていきます。まず、v-bind
を利用したバインディングは以下の通りです。
- HTML側:
<h2 v-bind:style="fontStyle">{{ title }}</h2>
で、fontStyle
とtitle
を表示するよう設定 - Vue.js側:
data
にてfontStyle
、title
をそれぞれ定義
双方向のバインディング(値を表示するだけでなく、入力を受け取る場合など)では、v-model
を利用します。また、各種イベントに処理を行うにはv-on
でイベントを捕捉し、methods
に定義した処理と結びつけます。
- HTML側:
<input type="text" v-model="todo" v-on:keyup.enter="addTodo">
で、属性todo
について双方向のバインディングを行う。また、v-on
によりエンターキーを押した際にaddTodo
を実行 - Vue.js側:
data
にてtodo
を定義、またmethods
にてaddTodo
を定義
ここまでで、表示・入力の取得・それに対する処理、という一連の流れを見てきました。非常にシンプルであり、アプリケーションを作成するのに最低限必要な要素は上記の短いサンプルの中にほとんど収められていると思います。
なお、テンプレートエンジンなどと組み合わせて使用する場合、Vue.jsのバインディングに使われる記号({{}}
)がテンプレートエンジン側のそれとバッティングすることがあります。この場合、Vue.config.delimiters
を設定することで回避ができます(詳細はこちら)。
また、レンダリングが完了するまでユーザーにHTMLを見せたくない場合はv-cloakを使用します。
2nd Step
次のステップとして、サーバーサイドとの連携を意識した実装を紹介します。ポイントとしては、以下の3点です。
- 初期化処理: ライフサイクルイベント
- 動的なプロパティ: パラメーターの変更に応じて計算
- 条件分岐処理: 条件に応じたレンダリング
サンプルとして実装したのはGitHubのIssueを取得するアプリです。
HTML
<div id="issues">
<h2>Latest {{repository}} Issues</h2>
<div>
<input type="text" v-model="repository">
</div>
<div>
<input type="text" v-model="searchText">
<div v-show="hasIssue">
<div v-for="issue in filtered_issues"
class="issue-default">
<a :href="issue.html_url" target="_blank">
{{issue.title}}
</a><br/>
<span>#{{issue.number}} at {{issue.updated_at | formatDate}}</span>
</div>
</div>
<div v-else>
The repository {{repository}} does not exist!
</div>
</div>
</div>
CSS
.issue-default{
padding: 10px;
}
.issue-default a {
font-weight: bold;
font-size: 18px;
text-decoration: none;
color:slategray;
}
.issue-default span {
color:#767676;
font-size: 12px;
}
JavaScript
var ISSUES = "https://api.github.com/repos/[R]/issues?state=open"
var app = new Vue({
el: "#issues",
data: {
repository: "vuejs/vue",
searchText: "",
issues: []
},
created: function () {
this.fetchData()
},
watch: {
repository: "fetchData"
},
filters: {
formatDate: function (v) {
return v.replace(/T|Z/g, ' ')
}
},
computed: {
hasIssue: function(){
return this.issues.length > 0 ? true : false;
},
filtered_issues: function(){
var query = this.searchText;
return this.issues.filter(function(issue){
return issue.title.indexOf(query) > -1
})
}
},
methods: {
fetchData: function () {
var xhr = new XMLHttpRequest();
var self = this;
xhr.open("GET", ISSUES.replace("[R]", this.repository));
xhr.onload = function () {
self.issues = JSON.parse(xhr.responseText);
}
xhr.send()
}
}
})
ポイントとして挙げた3点に沿って、解説をしていきます。
- 初期化処理:
created
にて、初期化処理(この場合Issueの取得処理)を実行 - 動的なプロパティ:
computed
にて、Issueが取得できているかに応じて計算する動的なプロパティを設定。また、入力された条件(searchText
)によって表示するIssueをフィルタリング。 - 条件分岐処理:
v-show
(v-if
)/v-else
にて、Issueが取得できているかで表示を変更
Vue.jsのライフサイクルイベントは、こちらで確認ができます。created
を利用することで、Vueのインスタンスができた後に行うべき処理を定義することができます。サーバーサイドからのデータ取得処理などはここで行うとよいです。
computed
は、ほかのプロパティから計算して得られるようなプロパティを定義できます。例としては、First NameとFamily Nameを足してFull Name、などです。
v-show
は表示を切り替えるためのものになります。v-if
もあるのですが、v-show
は単純に表示を切り替えるだけ、v-if
はレンダリングをそもそもしないという特徴があります(詳細はこちらをご参照ください)。
これらは、エラーページなどの表示を切り替える際に有効に使えます。
なお、今回は便利なFilter機能も使用しています。issue.updated_at | formatDate
というように、パイプ(|
)で適用を行うことができます。Filterの作り方、使い方についてはこちらをご参照ください。
3rd Step
最後に、Componentsについて述べておきます。これは、端的にはテンプレートのようなもので、再利用可能なパーツを定義するための仕組みです。
1st stepのコードをComponentsを使って書き直したものがいかになります。
HTML
<div id="todoApp">
<h2>Todo with Component</h2>
<input type="text" v-model="todo" v-on:keyup.enter="addTodo">
<span>doing {{doings}} / {{todos.length}} task</span>
<div>
<todo-template v-for="t in todos" :t="t" @todochanged="count"></todo-template>
</div>
</div>
<script type="text/x-template" id="todo-template">
<div v-bind:class="['todo-default', doing ? 'todo-doing' : '']"
v-on:click="begin">
{{t.name}}
</div>
</script>
JavaScript
Vue.component("todo-template", {
props: ["t"],
data: function(){
return {
doing: false
}
},
template: "#todo-template",
methods: {
begin: function () {
this.doing = !this.doing;
this.$emit("todochanged", this.doing);
}
}
});
var app = new Vue({
el: '#todoApp',
data: {
todo: "",
todos: [],
doings: 0
},
methods: {
addTodo: function() {
this.todos.push({
name: this.todo,
done: false
})
this.todo = ""
},
count: function(doing){
this.doings += doing ? 1 : -1;
}
}
})
Componentは、Vue.component
で定義を行います。
-
props
で変数を受け取る - HTMLでの受け渡し:
<todo-template v-for="t in todos" :t="t"></todo-template>
- Componentでの受け取り:
props: ["t"]
- Componentが独自にデータ(状態)を持つ場合、
data: function(){ ... return ...}
で定義(※こうしないと、全コンポーネント間でdata
が共有されてしまうので注意(詳細))。 - Component上で発生したイベントの、親への伝播
- 子側:
methods
にて、this.$emit
を利用しイベント(todochanged
)を発火(イベント名は小文字でないと発動しないっぽいので注意が必要) - 親側:
v-on
(@)で子側からのイベント発生時の処理を宣言しておく
Reactを触ったことがある人は、ReactのComponentにかなり近いのがわかるかと思います。なので、JSXが肌に合わないけど思想的には共感する、という方はVue.jsは良い選択肢だと思います。
他フレームワークへの展開
続いて、Vue.jsと他フレームワークのつながりについて見ていきたいと思います。
Knockout.jsとの関連
Vue.jsではVueクラスをインスタンス化してそれをHTML要素にバインドするというスタイルですが、これはKnockout.jsのスタイルとよく似ています(ViewModel型のフレームワーク)。
Vue.js/Knockout.js、何も生成したインスタンスが手元に残るため、そこからデータを取り出したり関数を呼び出したりするのが容易です。
そのため、外部からフレームワークの中で定義したものを読み込んだり呼び出したい場合に使い勝手が良いです(jQuery側の処理から呼び出すなど)。逆に、Angularはこの「外との連携」はかなり手間です。
既存のJavaScript処理資産がかなりあり、JavaScriptフレームワークの恩恵は受けたいけれどもそれで既存の資産が使えなくなるのはちょっと、という場合はVue.js/Knockout.jsはお勧めできるフレームワークです。
Knockout.jsの方が優れている点としては、以下の点になります。
- IEサポート(6-11)
Knockout.jsはIEの6-11をサポート(!?)しています。この優しさは他のフレームワークにはないものです。
逆に言えば、古いIE対応が必須な場合AngularやVue.jsにはそっとお別れを告げなければなりません。 - HTML5標準のバインディング
Vue.jsはv-
で始まる属性を設定することでバインディングを行っていますが、これはHTML標準には当然ない属性なのでエディタによっては警告が出たりします。Knockout.jsはdata-
で始まるHTML5標準の属性を利用するため、HTMLの完全性という意味ではメリットがあります。 - ko.util等による処理サポート
Vue.jsもdata
で宣言した配列に操作しやすいよういくつかメソッドの拡張が行われますが、Knockout.jsはそれをより強力にサポートするライブラリが付属しています。
ただ、逆にKnockout.jsはVue.jsに比べて記述がかなり煩雑になります。そのあたりを検討しつつ、どちらを採用するか検討するとよいと思います。
Angularとの関連
Vue.jsはDirective
(v-bind
など)と呼ばれる要素でバインディングを行ったり、Filter
によるインラインでの処理が可能ですが、この簡易な記述による豊かな処理はAngularの流れを汲んでいます。
こうした書きやすさ(生産性ともいえる)を重視するならば、Vue.js/Angularのルートをお勧めします。
Angularの方がすぐれている点としては、以下の点があげられます。
※現時点でのAngularを追っていないので、以下の記述には注意をしてください
-
Routing
AngularにはRouting機能があり、画面遷移を発生させずにページを切り替えるような処理が可能です。こちらはTutorial / 9 - Routing & Multiple Viewsに詳しいですが、まるでサーバーサイドのコントローラーのようなことができるのがわかると思います。
jQuery Mobileのように、モバイル向けでネイティブに近い動作を実現したいといった場合、この機能は有効に働くと思います。 -
Testable
ViewModel型のフレームワーク、特にVue.jsはel
の宣言からもわかる通りDOM構造と密接なつながりを持つためテストが難しくなる傾向があります。
Angularはモジュール同士が疎結合なので、テストがしやすいという特徴があります(Developer Guide / Unit Testing)。これはAngularの勘所ともいえる依存性注入(DI)により実現されていますが、これにより後述するモジュール性も非常に高くなっています。 -
Modulability
AngularはService/Factoryといった処理を切り出し部品化する仕組みが用意されており、高度なモジュール化が可能です。angular-xxxというモジュールが色々と開発されているのもこうした特性のためで、周辺エコシステムも含めて発展しているのもAngularの魅力と言えると思います。
以上の点から、SPAの開発などでRoutingに魅力を感じる場合、また大規模な開発でJavaScript側のコードを構造化しモジュールごとにテストを行いたい場合などにはAngularがお勧めできます。ただ、Vue.jsでも大規模開発のためのガイドが作成されており、この点はVue.jsでもカバー可能になってきています。
なにより、Angularは1系と2系があり、最新版である2系は2016/6現在まだβ版ですが(といっても1年くらい開発してますが)1系と大きく異なる変更が入るほか、TypeScriptの導入が必須になります。アプリの作成方法も大分手間になっており((5分で終わらない)5 Min QuickStart参照)、ちょっとしたアプリケーションの開発に使うにはお勧めできない感じです。格段の学習コストも含め、Angular=大規模開発向け、といっても差し支えないのではないかと個人的には思います。