Vue.js で 複数のViewModelを連携させる

追記: 2016/04/28

1.xのVueでは、Reactのようにpropsを利用することでコンポーネント間で値の受け渡しを行うことができます。
また2.0では、$dispatch, $broadcastは非推奨になるので、注意してください。 refs: 2.0 features · vuejs/vue Wiki · GitHub


こんにちは。りんご と にんじん と 生姜の入ったジュースを飲んでいます、きたけーです。

今日はVue.js で 複数のViewModelの連携について考えてみます。 VueはViewModel間の親子関係を定義することができますが、今回考えるのは(DOMで)親子関係がなく「リストからひとつの項目を選択したときに、その項目の詳細を別の要素で表示する」ようなケースです。

べたに書くと、こんなかんじになります(Coffeeです)。

listVM = new Vue
  data:
    list: []
  methods:
    selectItem: (e, index) ->
      detailVM.$data.item = @list[index]
  # ...

detailVM = new Vue
  data: 
    item: list[0]
  # ...  

が、リストのVMが詳細のVMに依存していて(密結合)、テストを書くときに複数のViewModelを生成するのが面倒だし、メンテナンス性も下がりますね。

イベントで連携する

Vueでは、異なるViewModel間の連携をViewModelをcomponentという単位にして、イベントでおこなうことを勧めているっぽいです( http://vuejs.org/guide/composition.htmlEvent Communication Between Nested Components )。 イベントでやりとりすることで疎結合にするのは、Vueに限らず、JSのコーディングでよくみられるやつですね。

component

componentは、Angular.jsのelement directiveのように汎用的に扱うために切り出したものと( http://vuejs.org/guide/composition.htmlRegistering a Component )、汎用的にするかは関係なく、ひとつのViewModelを複数の部分ViewModelに分割した場合の分割されたViewModelの両方を指すようです。後者の場合、ViewModelを生成するときのcomponentsオプションで指定することで、そのViewModel内だけでcomponentとして利用できます ( http://vuejs.org/guide/composition.htmlEncapsulating Private Assets )。

各ViewModelをcomponentにして、イベントでやりとりするように書き直すとこんなかんじです。

List = Vue.extend
  data:
    listData: []
  methods:
    selectItem: (e, index) ->
      @$dispatch 'selectItem', @listData[index]
  # ...

Detail = Vue.extend
  data: 
    item: list[0]
  # ...

appVM = new Vue
  components:
    list: List
    detail: Detail
appVM.$on 'selectItem', (item) ->
  @$.detail.$data.item = item

ViewModel, componentのユニットテスト

componentのユニットテストを書くときは、new Detail()のようにcomponentのオブジェクトを生成します。データを差し替えたかったらコンストラクタの引数でデータを渡すか(new Detail(data: {item: {}}))、生成後に$dataを通して変更します。

記事を書いている途中でこの記事をみつけました。.Net(C#)の記事ですが、MVVMアーキテクチャパターンについての説明なので、Vueでも同じことがいえるはずです。書いたVueのコードを眺めながら、以下の記事の引用部分を読むと、ViewModelのユニットテストがDOMに依存しないことの認識が深まりました。

一般的にViewModelは、C#などの汎用プログラミング言語で記述され、プレゼンテーション・ロジックとステート(=状態)を持ちます。
...
ViewModelは、Viewへの参照を持ったり、Viewの特定の実装を意識したりしません。しかしViewModelはまったくViewを意識しないというわけでもないので注意が必要です。「ViewModelはViewを意識しますが、その実装について何も知らなくてもよいし、知るべきではない」という認識が妥当です。