blog.bouzuya.net

2016-09-10 bouzuya/bbn-drawing-vuejs 0.1.0 をつくった

bouzuya/bbn-drawing-vuejs 0.1.0 をつくった。2016-09-07 の続きだ。Demo は Heroku に ある。

前回からの差分は次のとおりだ。

  • ViewModel と Model の分離
  • 通信処理の追加
  • 保存処理の追加

ViewModel と Model の分離は Message Bus っぽいものを置いた。実際には EventEmitter だ。bouzuya/b-o-a とは違って RxJS ではないので、 Command を Event に変換するような機構ではない。Command や Event を待って動く Handler を置いてあるところまでは一致しており、Handler の signature は異なる。最初そういう機構に持って行こうかと思ったが、無駄っぽいのでやめた。

Message Bus は次のとおり pubsub だけのものだ。

import { EventEmitter } from 'events';

/* ... */

const newMessageBus = (): MessageBus => {
  const subject = new EventEmitter();
  const publish: Publish = (message: Message): void => {
    // TODO: nextTick
    setTimeout(() => void subject.emit('data', message));
  };
  const subscribe: Subscribe = (listener: Listener): Unsubscribe => {
    const l = (message: Message): void => void listener(message);
    subject.on('data', l);
    return () => void subject.removeListener('data', l);
  };
  return { publish, subscribe };
};

export { newMessageBus };

ViewModel から Model へは EventEmitter.prototype.emit 相当だ。逆の Model から ViewModel は EventEmitter.prototype.on に加えて、差分適用のために deep-diff を使っている。変更のあった property だけを proxy object に反映してくれるはずだ。この用途だけなら、もっと簡単に済ませられそうだが、細かい点は気にしない。

ViewModel の例は次のとおりだ。つくりこんでいないので記述が冗長だ。ready / destroyed などを connect() で wrap すれば、react-redux のようになる。そこは簡潔になるかだけなので、構造上に大きな差はない。おそらく Vuex ならこのあたりをはじめ、うまくやってくれるはずだ。

/* ... */
const mountNewWorkVM = (state: Work, pub: Publish, sub: Function) => {
  return new Vue({
    el: `.work-${state.week}`,
    data: { state },
    ready(this: { unsubscribe: Function; state: Work; }): void {
      this.unsubscribe = sub((event: Event) => {
        if (event.type === 'updated') {
          const newState =
            event.state.works.find((w: Work) => w.week === this.state.week);
          merge(this.state, newState);
        }
      });
    },
    destroyed(this: { unsubscribe: Function; }): void {
      if (typeof this.unsubscribe !== 'undefined') this.unsubscribe();
    },
    methods: {
      decrement(this: { state: Work; }): void {
        pub(decrementCommand(this.state.week));
      },
      increment(this: { state: Work; }): void {
        pub(incrementCommand(this.state.week));
      }
    }
  });
};

ほかには通信処理を追加した。起動後に version を確認し、未更新なら Issue 登録への link を表示する。特に難しいところはなく Handler を追加するだけだ。

保存処理を追加した。状態に変更があるたび Cookie に保存する。これも Handler を追加するだけだ。

前回と結論は同じだけど、Vue.js は悪くない View / ViewModel 向けの library だ。それに今回の制約である「最初から HTML がある」という点に無駄なく合わせていける。Flux のような構造を実現するには工夫が必要だけど、そこはそもそも Vue.js の範囲外だ。今回は比較的うまく Model を切り出すことができた。Model は Handler の構造だけに依存している。Handler の構造からの切り離しは容易なので、これ以上は不要だろう。