2015/05/27

【TypeScript】MVVMなVue.jsを使ってToDoアプリをつくってみた

今まで素のJavaScriptとAngularJSを使って、Todoアプリをつくった。

素のJavaScriptはとにかくコード量が多くなって面倒。
AngularJSは学習コストが高く、AngularJS2.0になると別モノになってしまう。

とにかく手軽にアプリが作れるフレームワークを、と探したところにMVVMフレームワークの「Vue.js」を見つけた。
ググるとなにかと「お手軽」というキーワードが目についたので、実際に使ってみた。

できあがったモノは、以下のサイトで触れる。

ToDoアプリ自体は、以下の2つのエントリとほとんど同じ。





準備


bower、tsd、NuGet、GitHubなどから以下のjsファイルと型定義ファイルをダウンロードする。
  • vue
  • vue.d.ts


View(HTML)


<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Vue.js + TypeScript</title>
    <style>
        .done-true{
            color: gray;
            text-decoration: line-through;
        }
    </style>
</head>
<body>
    <div id="todoapp">
        <h1>Vue.js + TypeScript</h1>
        <p>Finished Task: {{getDoneCount}} / {{tasks.length}}</p>
        <button v-on="click: deleteDone">Delete Finished</button>
        <ul>
            <li v-repeat="task: tasks">
                <input type="checkbox" v-model="task.done" />
                <span class="done-{{task.done}}">{{$index}}:{{task.body}}</span>
                <a href="#" v-on="click: deleteTask($index)">[x]</a>
            </li>
        </ul>
        <form onsubmit="return false;" v-on="submit: addNew">
            <input type="text" v-model="newTaskBody" />
            <input type="submit" value="add" />
        </form>
    </div>    
    <script src="./bundle.js"></script>
</body>
</html>

見た目、AngularJSとほとんど変わらない。
「ng-hoge」が「v-hoge」に変わり、「ng-click」が「v-on="click hoge"」に変わっただけ。

ウチの環境では、webpackを使ってjsファイルをバンドル化しているので、scriptの読み込みが1行になっている。
実装したjsファイルとライブラリは分けたほうが良いと思うんだけど、どうなんだろう?



ViewModel (TypeScript)


ViewModelとは、Viewを描画するための状態の保持と、Viewから受け取った入力を適切なカタチに変換してModelに伝達する役割。
だけど、ToDoアプリ自体小さなプログラムなので今回はModel層はなし。
/// <reference path="./typings/vue/vue.d.ts" />

import Vue = require('vue');

interface ITask {
    body: string
    done: boolean
}

class TodoApp extends Vue{
    // タスク一覧
    tasks: ITask[];
    // 新規タスク
    newTaskBody = '';
    
    constructor() {
        // to defer compilation in Vue
        super(false);
        
        this._init({
            el: '#todoapp',
            data: {
                tasks: this.tasks,
                newTaskBody: this.newTaskBody
            },
            computed: {
                getDoneCount: this.getDoneCount
            },
            created: () => {
                this.tasks = [
                    { body: 'do this 1', done: false },
                    { body: 'do this 2', done: false },
                    { body: 'do this 3', done: false },
                    { body: 'do this 4', done: false }
                ];
            },
            methods: {
                addNew: this.addNew,
                deleteTask: this.deleteTask,
                deleteDone: this.deleteDone
            }
        });
    }
    
    /**
     * タスクの追加
     */
    addNew(): void{
        var task = this.newTaskBody && this.newTaskBody.trim();
        if (!task) {
            return;
        }
        this.tasks.push({ body: task, done: false });
        this.newTaskBody = '';
    }
    
    /**
     * タスクの削除
     * @param index :削除する行番号
     */
    deleteTask(delIndex: number): void{
        this.tasks.splice(delIndex, 1);
    }
    
    /**
     * 済タスクの一括削除
     */
    deleteDone(): void{
        var oldTasks = this.tasks;
        this.tasks = [];
        
        oldTasks.forEach(task => {
            if (!task.done) this.tasks.push(task);
        });
    }
    
    /**
     * 完了済み件数の取得
     * @return 完了済み件数
     */
    getDoneCount(): number{
        var count = 0;
        this.tasks.forEach(task => {
            count += task.done ? 1 : 0;
        });
        
        return count;
    }
}

export var todoApp = new TodoApp();

1行目:型定義ファイル(vue.d.ts)の読み込み

3行目:前述の通り、Webpackを使っているので「import Vue = require(‘vue’)」なんてことをしている。

5行目:ひとつのタスクを表すinterface。それぞれのタスクに何かする機能を付けたいならclassにした方が良い。

10行目~87行目:ViewModelの実装

10行目:いろいろ実装するために、Vueを継承する。

12~14行目:ViewModelで保持するデータ(タスク一覧と新規タスク名)

18行目:Vueのコンストラクタにfalseを渡すと、コンパイルを遅延させられる。

20行目:見覚えのあるVueの定義は、this._initをオーバーライドするカタチで行う。
  • el:対象となるHTML要素(Element)
  • data:バインドする値
  • computed:dataの値を利用した計算式のようなモノ
  • created:初期化処理などに使う
  • methods:イベントなどから呼び出す各種処理

48~88行目:computedやmethodsにバインドするメソッドの実装

91行目:最後にTodoAppクラスをインスタンス化してexportして終わり。インスタンス化だけでexportをしないと動かないので注意


※ 追記:2015/06/25 20:30
91行目:webpackを使わない場合は、export をすると「exports is not defined」というエラーが発生する。exportなしで「var todoApp = new TodoApp();」と書いたら動いた。



さいごに


今回作成したToDoアプリをGitHubにあげた。

AngularJSと比較すると、$scopeとかよくわからないモノがない分、理解しやすかった。
慣れもあるのかもしれないが、直感的で書きやすい印象だった。

Componentを使った実装や、Modelの存在をないものとして扱ってきたが、今後機会があったらまたブログにまとめようと思う。

とにかく手軽にWebアプリケーションがつくりたい!というなら、Vue.jsは良い選択肢になると思う。



参考サイト




以上

written by @bc_rikko

0 件のコメント :

コメントを投稿