Vue.jsから手軽に始めるJavaScriptフレームワーク

  • 828
    いいね
  • 7
    コメント

Vue.jsはそのまま使ってもよいですが「自分(プロジェクト)に合ったフレームワーク」を見つけるのにも向いています。

これは、後発フレームワークだけあり各フレームワークの特徴を意識した設計がなされているためです。他の著名なフレームワークとの特徴を比較した文書もあるので、こちらをチェックしながら導入を検討するとよいと思います。

そのため、以下はVue.jsの紹介と他フレームワーク(Knockout.js と Angular.js)へのステップという2セクションに分けて紹介していきたいと思います。
JavaScriptフレームワークの導入を行いたいがこの選択は慎重にいきたい、という状況であれば最初にVue.jsを試金石としてみて、効果的と感じられる機能からAngular.jsやKnockout.jsに流れていくというのは十分ありだと思います。

  • 2016/06: Vue.jsの1.x系に合わせて記述・サンプルをアップデートしました
  • 2017/06: Vue.jsの2.x系に合わせて記述・サンプルをアップデートしました

3Stepで学ぶVue.js

1st Step

まずはシンプルな例。以下のJSFiddleで動かしながら確認できます。

vue.js step1

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>で、fontStyletitleを表示するよう設定
  • Vue.js側: dataにてfontStyletitleをそれぞれ定義

双方向のバインディング(値を表示するだけでなく、入力を受け取る場合など)では、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を取得するアプリです。

vue.js step2

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点に沿って、解説をしていきます。

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について述べておきます。これは、端的にはテンプレートのようなもので、再利用可能なパーツを定義するための仕組みです。

vue.js step3

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.jsとの関連

Vue.jsはDirective(v-bindなど)と呼ばれる要素でバインディングを行ったり、Filterによるインラインでの処理が可能ですが、この簡易な記述による豊かな処理はAngular.jsの流れを汲んでいます。
こうした書きやすさ(生産性ともいえる)を重視するならば、Vue.js/Angular.jsのルートをお勧めします。

Angular.jsの方がすぐれている点としては、以下の点があげられます。

※現時点でのAngularを追っていないので、以下の記述には注意をしてください

  • Routing
    Angular.jsにはRouting機能があり、画面遷移を発生させずにページを切り替えるような処理が可能です。こちらはTutorial / 9 - Routing & Multiple Viewsに詳しいですが、まるでサーバーサイドのコントローラーのようなことができるのがわかると思います。
    jQuery Mobileのように、モバイル向けでネイティブに近い動作を実現したいといった場合、この機能は有効に働くと思います。

  • Testable
    ViewModel型のフレームワーク、特にVue.jsはelの宣言からもわかる通りDOM構造と密接なつながりを持つためテストが難しくなる傾向があります。
    Angular.jsはモジュール同士が疎結合なので、テストがしやすいという特徴があります(Developer Guide / Unit Testing)。これはAngular.jsの勘所ともいえる依存性注入(DI)により実現されていますが、これにより後述するモジュール性も非常に高くなっています。

  • Modulability
    Angular.jsはService/Factoryといった処理を切り出し部品化する仕組みが用意されており、高度なモジュール化が可能です。angular-xxxというモジュールが色々と開発されているのもこうした特性のためで、周辺エコシステムも含めて発展しているのもAngular.jsの魅力と言えると思います。

以上の点から、SPAの開発などでRoutingに魅力を感じる場合、また大規模な開発でJavaScript側のコードを構造化しモジュールごとにテストを行いたい場合などにはAngular.jsがお勧めできます。ただ、Vue.jsでも大規模開発のためのガイドが作成されており、この点はVue.jsでもカバー可能になってきています。
なにより、Angular.jsは1系と2系があり、最新版である2系は2016/6現在まだβ版ですが(といっても1年くらい開発してますが)1系と大きく異なる変更が入るほか、TypeScriptの導入が必須になります。アプリの作成方法も大分手間になっており((5分で終わらない)5 Min QuickStart参照)、ちょっとしたアプリケーションの開発に使うにはお勧めできない感じです。格段の学習コストも含め、Angular.js=大規模開発向け、といっても差し支えないのではないかと個人的には思います。