軽量でパワフルなデータバインディングMVVM, vue.jsで遊んでみた

Vue.jsは軽量なMVVMライブラリ。

vue.js http://vuejs.org/

使ってみた感じ、かなり手触りがよいので、紹介する。

概要

handlebars風のテンプレートを書いて、DOMを展開する。普通のテンプレートエンジンと違い、$dataアクセッサを通じて値を書き換えることで、テンプレート展開後も値が同期する(!!!)。

一言で言うと軽量Angular。コード読んだ感じ、内部的にもAngularから大量にコードを持ってきた痕跡がある。$watchとdirective定義がキモなのは同じ。

とはいっても、軽量なのは使う側のAPI側だけで、内部実装はそれなりに重い。APIを軽量にすることで、Angularのデメリットである学習コスト部分を限りなく削ることを目標にしているんじゃないだろうか。

大雑把な使い方

テンプレートを書く。対応するデータ構造を書く。{foo: 'bar'}と書いたら{{foo}}にとにかく値が同期する。

ついでにAngular風のディレクティブを書く。たとえば <input v-model='foo'/> と書いたら入力フィールドの値がfooと同期する。そんな感じのディレクティブが複数ある。

ピュアなJSのデータ構造を監視できるから、JS側からは監視されてることをあまり意識しなくても使える。(knockoutと比べた際のメリット。内部的にはgetter定義しまくって実現している)

いくらかdirective定義を覚える必要があるが、どうせ自分で定義することになるし、そこの学習コストはちょっとずつで良い。基本的にはv-onとv-repeatぐらいで良い。

コードを書いてみる。

サンプルプロジェクトを作った

mizchi/try-vue.js https://github.com/mizchi/try-vue.js

こんなの

テンプレートをこういう風に書く。

<h1>Hello, {{message}}</h1>

<p class="{{message}}">color</h1>
<input v-model="color"></input>

<div class="counter" style="color: {{color}};">
  <p> counter: {{counter}} </p>
</div>

<button v-on='click: addTweet'>add tweet</button>
<ul class="tweets">
  <li v-repeat="tweets" v-on='click: erase(this)' data-index='{{$index}}'>
    {{name}}:{{$index}}:{{content}}
  </li>
</ul>

ロジックを注入(coffee)

Vue::attach = (selector) -> $(selector).append @$el

class Tweet
  constructor: (@name, @content)->
  erase: ->
    console.log 'selected', @$index
    @$parent.$data.tweets.splice @$index, 1

$ ->
  content = new Vue
    template: template
    data:
      message: 'vue.js!'
      counter: 0
      color: 'red'
      tweets: []
    methods:
      addTweet: ->
        @$data.tweets.push new Tweet 'foo', Date.now()

  setInterval ->
    content.$data.counter++
  , 1000

  content.attach 'body'

ハマったところ

v-repeatの中で v-on でコールバック定義する場合、自分自身の$indexを取得したい場合は v-on='click:erase(this)'みたいにする。このthis、どこにも言及がなくてサンプルプロジェクト眺めてて気づいた。

最後に

良いので使おう どうでもよいけど、自分のGithubがサンプルプロジェクトだらけになるのどうにかしたい…