NHN Cloud NHN Cloud Meetup!

JavaScriptフレームワークの概要3 – Vue.js

これから4回にわたり、JavaScript(フロントエンド)フレームワークについて紹介したいと思います。以下のような内容で連載する予定です。

  1. Cycle.js
  2. Angular 2
  3. Vue.js
  4. React

Vue.js

はじめに

Vue.jsは、Evan Youによって作成され、2014年のリリースを皮切りに着実に発展しているJavaScriptフレームワークです。先に紹介したAngularやReactよりも認知されています。Vue.jsはその発音の通り、徹底してビュー(View)に最適化されたフレームワークで、コントローラの代わりに、ビューモデルを持つMVVM(Model-View-ViewModel)パターンを基盤にデザインされています。コンポーネントを使って再利用可能なUIをまとめ、ビューレイヤーを整理することを最も強力な機能として挙げています。また、テンプレート中心の開発を推奨しています。これらの特徴はサンプルコードを見ながら1つずつ理解していきましょう。これから紹介するサンプルはVue.js2.0に基づいて作成しています。

開発/駆動環境

依存性

Vue.jsを使用するときに注意すべき依存性は特にありません。2.0からVirtual DOMの実装チェーンSnabbdomを使用していますが、必要に応じて修正ソースに内蔵されており、個別にインストールしたり、バージョンを気にする必要はありません。Vue.jsは、テンプレートエンジンを別途使用せず、ウェブコンポーネントのスペックと同様に実装されたHTML形式のカスタム要素を利用して、Virtual DOMレンダリング関数でコンパイルするように内部実装しました。

追加ツール

vue-cli

vue-cliはプロジェクトを簡単に構成できるように、あらかじめ定義された設定を使用できます。Vue.js専用のYeomanと考えるとよいでしょう。

$ npm install -g vue-cli

npmを使って簡単にインストールができ、下記のような基本設定を提供しています。

  • webpack :Webpack、vue-loaderは、静的解析、テストなどの基本的なビルドプロセスの大半を設定してくれる
  • webpack-simple :Webpackとvue-loaderで構成されたシンプルな組み合わせ
  • browserify :Browserify、vueifyは、静的解析、テストなどの基本的なビルドプロセスの大半を設定してくれる
  • browserify-simple :Browserifyとvueifyで構成されたシンプルな組み合わせ
  • simple :特別なモジュールの管理ツールを使わず、HTMLファイルのみで構成する最も簡単な組み合わせ

以下のように使用します。

$ vue init webpack projectName

プロジェクトの規模や必要に応じてオプションを選択しましょう。あらかじめ設定されているものを利用できるので、いちいち設定するストレスがありません。

Vuex

Vue.jsは、インスタンス作成時に引数として渡されるdataオブジェクトを使って状態を管理しますが、dataの中にある内容が変更されると、内部で検知して、当該データを使用するコンポーネントが自らアップデートします。このとき、デバッグの便宜上、dataに直接アクセスせず別途ストアパターンを使ってデータを管理できるオブジェクトを実装して、最終的にFluxアーキテクチャと似た構造も考慮できます。このように簡単にストアを実装できますが、直接作成せず、FluxとElmアーキテクチャからインスピレーションを得て、Vue.jsを拡張したVuexも使用することができます。

vue-devtools

最近のフロントエンドフレームワークはそれ自体も重要ですが、開発時に使用する開発ツールにも必須で対応するようになったようです。Vue.jsも、ReactとReduxの開発ツールのみ有用なChrome専用の開発ツールのプラグインに対応しています。react-devtoolsのように、カスタム要素の階層構造とバインドされたコンポーネントの情報を確認でき、Vuex関連ツールもサポートしています。

(出典:https://github.com/vuejs/vue-devtools

単一ファイルコンポーネント用のビルド環境

Vue.jsは、単一ファイルコンポーネント(Single File Components)を提供していますが、これを正常に使用するには、WebpackやBrowserifyを使って構築した後、ブラウザで使用可能なJavaScriptを1つのファイルにバンドルする過程が必要になります。ここで必要となる各ツールの設定は、手動で行うことはなく、vue-cliを利用して簡単にセッティングできます。

JSXを使用するためのビルド環境

Reactで使用するJSXをVue.jsでも利用したい場合は、render関数を作成して、Babelプラグインを用いることでバンドルの段階で適用できます。
実装コードは、Reactとよく似ています。

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
    el: '#demo',
    render (h) {
        return (
            <AnchoredHeading level={1}>
                <span>Hello</span> world!
            </AnchoredHeading>
        )
    }
})

駆動環境

React、Angular 2と同様に、大半のモダンブラウザに対応しており、Internet Explorer 9から対応しています。

アーキテクチャ

Vueコンストラクタ

ビューモデル(View Model)はVue.jsの基礎で、絶対に見逃せない概念の1つです。ビューモデルは、以下のように要約できます。

MVVMパターンのVMに対応し、MVCパターンでは、コントローラの役割のようにデータを管理してアクションを処理する。

(出典:https://en.wikipedia.org/wiki/Model-view-viewmodel

Vue.jsはVueコンストラクタ関数のインスタンスを生成し、ビューモデルを扱うことになります。次のサンプルのようにインスタンスを生成するときに、ビューとデータを接続するオプションを設定できます。よく使われるオプション情報を紹介しましょう。その他、多くのオプションは、APIドキュメントOptionsカテゴリーから確認できます。

  • ビュー関連オプション:eltemplate
  • データ関連オプション:datamethodscomputed
  • コンポーネント関連オプション: components
  • ライフサイクルフック:createdmountedupdateddestroyed

ビューモデルのインスタンスが正常に生成されたら、Vue.jsの事前準備は半分終わります。

var vm = new Vue({
    el: '#example', // DOM
    data: { // Plain Data
        firstName: 'Foo',
        lastName: 'Bar'
    },
    methods: {
        getFullName: function() { // Methods For Data
            return this.firstName + ' ' + this.lastName;
        }
    },
    created: function() { // Lifecycle Hooks
        console.log(this.firstName, this.lastName);
    }
    ...
});

Vue.jsでビューモデルが生成された瞬間から重要なサイクルが始まります。ライフサイクル(Lifecycle)と呼ばれ、次の図で上記のビューモデルを作成するために設定したオプションが、どのような用途に使用されるか把握できるでしょう。図で、赤色のボックスは、ライフサイクルの段階で、ビューモデルを作成するオプションのライフサイクルフックに対応するコールバックメソッドが実行される時点でもあります。ライフサイクルフックを基準にライフサイクルの各段階の処理内容を整理します。

  • beforeCreatecreated:データとイベントの初期化
  • createdbeforeMount:ビューの作成
  • mountedupdated:データバインディング、データの変更、ビューの更新
  • destroyed:子コンポーネント、イベントリスナーを解除

(出典:http://vuejs.org

テンプレート

Vue.jsのデータを外部に表示するにはどうすればよいでしょうか?ここで、ビューモデルの作成に必要なオプションの中から、templateあるいはelのプロパティを思い出してみましょう。Vue.jsでデータはレンダリングされたDOMにバインドされて処理されます。このような処理のためにテンプレートが活躍するので、HTML基盤のテンプレートを使用することをお勧めします。これはJSXのような文法を別に学ばなくても、アプリケーション開発者が知っているHTMLの基礎知識でプログラミングができるということです。(もちろんJSXの使用も可能です)

以下は、ビューモデルのテンプレートを実際のDOMとマッピングして、テンプレートにデータ値nameをバインドして<span>に出力するサンプルです。Mustaches{{ }}タグの中にdataオブジェクトのプロパティ値を出力すればOKです。一般的なJavaScriptのテンプレートエンジンを使用する方法と同じですね。サンプルを実行すると、「My framework is Vue.js!」という文章が現われるでしょう。

<div id="example">
    <p>My framework is {{ name + '!' }}</p>
</div>
var vm = new Vue({
    el: '#example',
    data: {
        name: 'Vue.js'
    }
});

ここで少しスパイスを加えてテンプレートを反応的(Reactive)にすることができます。このとき必要なものが、ディレクティブ(Directives)です。ディレクティブは、HTML、コンパイラが実行されるときに、DOMを探すのに使われる特別な型の属性(Attribute)です。Vue.jsのディレクティブは、プレフィックスv-で始まり、スタイルからイベントまでDOMを扱うことができるすべての範囲で、様々なディレクティブを提供します。ディレクティブのエレメントに特定のアクションが発生したとき、ディレクティブに割り当てられたJavaScriptの構文が実行され、状況に応じてデータとテンプレートのステータスが変更されます。前のサンプルにv-onディレクティブを含むボタンを追加した後、クリックイベントを発生させると、name値が自動で更新されます。

<div id="example">
    <p>My framework is {{ name + '!' }}</p>
    <button v-on:click="changeName">Change</button>
</div>
var vm = new Vue({
    el: '#example',
    data: {
        name: 'Vue.js'
    },
    methods: {
        changeName: function() {
            this.name = this.name.toLowerCase(); // Vue.js --> vue.js
        }
    }
});

データフロー

次に深い範囲でデータを見てみよう。ディレクティブを説明する部分で、反応的(Reactive)という表現を使用しましたが、これはVue.jsがデータを扱う方式を明確に表しています。Vue.jsでは、データが変更されたときに、そのデータを参照しているビューも一緒に更新されます。手動でDOM要素を検索してテキストを変更する手間をVue.jsが代わりに処理してくれます。どうしてこのような動作ができるのでしょうか?

ビューモデルのインスタンスはwatcherオブジェクトを含み、インスタンスのオプションのうち、dataはVue.jsのデータとして純粋なJavaScriptのオブジェクトです。watcherの役割は、データの変更を監視して値が変更されると、ディレクティブにこれを通知し、そのディレクティブを含むDOMを更新できるようにサポートします。バージョン2.0では、Virtual DOMを使ってDOMの更新方法が変更されましたが、watcherという重要なゲートを置いたことで、データが移動されるキーは変更されません。

下図は、1.0(上)、2.0(下)のバージョンでのデータフローです。
(出典:http://vuejs.org

コンポーネント

コンポーネントは、Webアプリケーションを構成する最小単位で、いかにUIを美しく整理して再利用できるか、尺度となる重要な機能です。Vue.jsもコンポーネントが提供されています。今回の目次を別々に分類した理由は、アーキテクチャの説明ビューモデルとテンプレートをもとに、コンポーネントが生成されるからです。アプリケーションレベルのステップから始めてみましょう。
(出典:http://vuejs.org

ビューモデルのインスタンスの生成から始まります。生成されたビューモデルのインスタンスは、登録しようとするコンポーネントの領域を指定して、コンポーネントを管理します。コンポーネントの目的は、共通のテンプレートを使って、複数のデータを表現することなので、グローバル領域にコンポーネントを登録する必要があります。(地域的にも登録して使用できますが「共通」という単語と距離がありそうなので、今回の説明では除きます)

このとき、新しい概念が登場します。カスタム要素にコンポーネントを肉付けする一種の骨組みだと考えると理解しやすいでしょう。コンポーネントの肉付けはテンプレートに該当し、コンポーネントが登録されるときに、マッチングするカスタム要素を見つけてテンプレートに切り替えます。サンプルでは、<my-component>がカスタム要素で、コンポーネントを登録した後<p>My framework is Vue.js!</p>に変更されます。カスタム要素の代わりにディレクティブを使って、実際のDOMにマッピングすることもありますが、詳しい使用方法は、こちらを参照してください。

<div id="example">
    <my-component></my-component>
</div>

// コンポーネント登録
Vue.component('my-component', {
    template: '<p>My framework is Vue.js!</p>'
});

// ビューモデルインスタンス生成
var vm = new Vue({
    el: '#example'
});

今回は、データをバインドして、再利用可能なコンポーネントを作成してみます。

次のサンプルを見るとイメージできるでしょうか?3つのボタンの外観は同じですが、各コンポーネントが参照するデータが異なり、アクションに応じて、それぞれのデータが変化することになります。10個のボタンを登録するといっても、コンポーネントの登録は1回で十分です。コンポーネントのデータはビューモデルと同様にdataオプションで管理され、Vueインスタンスのdataと異なり、純粋なオブジェクトの代わりにコールバック関数から返された値を使用します。これは、それぞれのコンポーネントが同じデータを共有していないためです。これらの概念を活用すると、複数ページの中で共通使用できるUI(例えば掲示板のリストなど)を分離して、データのみ交換して使用できます。反復的なものは、すべてVue.jsのコンポーネントに任せるとよいでしょう。

<div id="example">
    <simple-counter></simple-counter>
    <simple-counter></simple-counter>
    <simple-counter></simple-counter>
</div>
Vue.component('simple-counter', {
    template: '<button v-on:click="increase">{{ counter }}</button>',
    data: function() {
        return {
            counter: 0
        };
    },
    methods: {
        increase: function() {
             this.counter += 1;
        }
    }
});

var vm = new Vue({
    el: '#example'
});

上のサンプルのように、常に同じレベルに位置するコンポーネントのみ生成できるわけではありません。コンポーネントは、別のコンポーネントを含むこともでき、1つのテンプレートの中で、親-子の関係を形成しながら、さまざまな種類のコンポーネントの生成と組み合わせが可能です。

ここで注目すべき点は、データの通信方式です。親コンポーネントが持つデータは、子コンポーネントでも使用でき、親から変更が起きたとき、関連する子コンポーネントが最新の状態に合わせて更新される特性があります。子コンポーネントが更新されたとき、親に変更されたデータを渡す代わりに「変更された」ことを通知するイベントを発生させます。このような関係を、一方向イベントフロー(One-Way Data Flow)と呼び、規模の大きいアプリケーションでは、データフローを簡単に作成できるメリットがあります。

(出典:http://vuejs.org

上記の内容を実装したサンプルコードです。vmインスタンスは、親の役割をしながら子コンポーネントにchild-componentを保有します。このとき、子コンポーネントのpropsプロパティから親のデータを伝達します。親のデータmessageは、子コンポーネントで参照でき、ボタンをクリックしたときmessageの値が変更され、子コンポーネントのビューで表示される値も自動更新されます。

<div id="example">
    <child-component v-bind:message="message"></child-component>
    <button v-on:click="changeMessage">Change</button>
</div>
Vue.component('child-component', {
    template: '<p>{{ fullMessage }}</p>',
    props: ['message'],
    data: function() {
        return {
            name: 'Vue.js',
        }
    },
    computed:{
        fullMessage: function() {
            return this.message + this.name + '!';
        }
    }
});

var vm = new Vue({
    el: '#example',
    data: {
        message: 'My component is '
    },
    methods: {
        changeMessage: function() {
            this.message = 'Your component is ';
        }
    }
});

Vue.jsはコンポーネントをさらに効率的に管理できる方法も提供しています。Vue.componentAPIでコンポーネントを作成する方法は、中小規模のプロジェクトに適しています。しかし、いくつか注意点があります。

  • すべてのコンポーネントは、重複しない固有の名前を持つ必要がある
  • テンプレートをマルチラインで定義するとき可読性が落ちるスラッシュ(/)を使用しなければならない
  • CSSもモジュール化が難しい

よって、vue拡張子で保存されている単一ファイルコンポーネントを使って、1つのファイルにテンプレートから、CSSスタイル、コンポーネントまですべてを定義してモジュール化することができます。単一ファイルコンポーネントを使うと、コンポーネントの物理的な管理が容易になります。またjadeTypeScriptSCSSなどの特定の言語も使用できます。

(出典:http://vuejs.org

最後に、ルーティングまで加えると、Vue.jsのコンポーネントの機能は完全になります。最小単位のコンポーネントをまとめて組み合わせるとVue.jsだけのSPAが完成します。

テスト

バンドリング環境だけでなくテスト環境も初めて構成するとき、いくつかの試行錯誤を経験して、かなりの時間を浪費することになります。このような場合、vue-cliからあらかじめ構成されたテスト環境を簡単に利用することができるので、この方法を推奨します。どのような組み合わせのテスト環境も適用できますが、vue-cliでは最も普及したKarmaとテストフレームワークの組み合わせで環境を作ってくれます。vue-cliを用いてWebpackやBrowserifyをフル設定でインストールする必要があり、インストールを始める前に、端末からのMocha、あるいはJasmineでテストフレームワークを選択できます。

$ npm run test

上記のスクリプトを実行すると、あらかじめ設定されたテスト環境によって単体テストが1つずつ実行されます。基本的にPhantomJS環境のみ設定されていますが、Karmaプラグインを追加インストールして、さまざまなブラウザ環境も簡単に追加することができます。

コンポーネントのテストケースを作成するとき、コンポーネントにオプションをそれぞれのケースに合わせて異なる方法で適用した後、コンポーネントの中で作られるVirtual DOMオブジェクトを確認する形で単体テストを作成します。

import Vue from 'vue';
import Hello from 'src/components/Hello';

describe('Hello.vue', () => {
   it('should render correct contents', () => {
       const vm = new Vue({
           el: document.createElement('div'),
           render: (h) => h(Hello)
        });
        expect(vm.$el.querySelector('.hello h1').textContent).to.equal('Hello Vue!');
    });
});

上のサンプルでは、非常に単純な単一ファイルコンポーネントをテストするもので、コンポーネントインスタンスの$elでVirtual DOMオブジェクトにアクセスして、DOMをテストするように検証できます。

パフォーマンス

Vue.jsは他のフレームワークとの性能比較、その結果も提供しており、フレームワーク適用時のパフォーマンスを考慮する方には大いに役立つことでしょう。
(出典:http://vuejs.org

Vue.jsは反応的(Reactive)であり、ビューコンポーネントを提供するという類似点により、Reactとよく比較されます。これらの数値は、Reactとレンダリングのパフォーマンス比較をした結果です。様々な状況でのレンダリング速度を測定し、どの区間を観測しても、ReactよりVue.jsのレンダリング速度が速いことが分かります。Vue.jsバージョン1.0では、Virtual DOMの代わりに実際のDOMテンプレートを使用する構造であるため、またバージョン2.0では、メモリ消費とパフォーマンスを改善したVirtual DOMを導入することにより、Reactよりも高いパフォーマンスを見せます。

Angular 1と比較しても優勢です。Angular 1の場合、Dirty Checkingを行いますが、Vue.jsは非同期キューオブザーバーシステムを利用して依存関係が明示的でない場合には、独立してイベントを発生するように処理します。そのため、速いです。Angular 2と比較すると、パフォーマンスに大きな違いはないと言及されていますが、Vue.jsが23kbの軽量フレームワークという点でリードしています。

まとめ

Vue.jsの開発者たちは、引き続きそのフレームワークをもっと見やすく快適に使用できるように改善しています。これはガイドページGitHubのログを見てもよく分かります。Vue.jsの目標は、フロントエンドの開発者が気軽に登録して簡単にアプリケーションを生産できるように支援することではないかと推測できます。

Vue.jsは他のフレームワークに比べて複雑ではありません。Vue.jsを使用するのに最初から難しい概念を身につける必要はなく、必要に応じてプラグインライブラリを提供するため柔軟性があります。また、ユーザー中心のフレームワークであることは明らかで、バージョン2.0の更新履歴を見ると、Virtual DOMを導入するなど、内部的に大きな変化がありましたが、APIを含むカプセル化領域ではほとんど変動がありませんでした。ユーザーを混乱させないためのそれなりの配慮だと思われます。(移行方法も紹介します)
Vue.jsは親切で使いやすいと思います。新しいアプリケーションが必要な場合は、試しにVue.jsをインストールしてみてください。

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop