ぬまのそこ

namazuのゆるいエンジニアブログ

Vue.jsでcomputed以外のプロパティでcomputedっぽいことをする

標題のとおりです。

したいこと

export default {
    name: 'hoge-component',
    computed: {
        hoge ( ) {
          return 'xxx';
        }
    },
    meta: { // これがcomputedのように動いて欲しい
       reactiveComputed ( ) { // => 'xxx'
          return this.hoge;
       }
    }
}

普段はcomputed内だけに定義できる算出ゲッターをmeta内に定義できるようにします。

動的に変わりまくるtitleやpageContext的なものを扱うためにcomputedではないレイヤーにcomputedがほしかった。

やりかた

  1. globalMixinを使い,createdでmetaが入ってきたコンポーネントが作られたことを検出します。
  2. metaのそれぞれの関数にコンポーネントをbindして作った関数で別のVueインスタンスを作ります
  3. 新しく作るVueインスタンスのcomputedに登録してWatcherで追跡してもらいます
watcher = undefined;
Vue.mixin({
  created() {
    if (this.$options.meta) {
      const self = this;
      const meta = this.$options.meta;
      const computed = Object.entries(meta).reduce((acc, current) => {
        if (typeof current[1] === "function") {
          acc[current[0]] = current[1].bind(self);
        }
        return acc;
      }, {});
      watcher = new Vue({
        computed,
        watch: {
          hoge(to, from) {
            console.log(to);
          }
        }
      });
    }
  },
  beforeDestroy () {
    if (watcher) {
      watcher.$destroy();
    }
  }
});

これでok. リアクティブな値を好きなスコープに作るのはVue.util.defineReactiveで作れますが, computedを作るにはWatcherをnewするひつようがあるので別のVueインスタンスを作るしかなさげかなと。 Vue.prototype._initをいじればcomputedにmetaの中身をマージできますが, そうすると同名でcomputedに置けなくなってしまう。

こうすればインスタンスが増えてしまうけれどcomputedを作れます。

コード

Vue Template - CodeSandbox

さらに

computedと同時に同名のwatchを定義して, 別につくったVueインスタンスのdataを置き換えるようにします。 そうすると散らばったコンポーネントのmetaが一箇所にまとまります.

このVueインスタンスのdataを元に色々と処理をします。  そうすると各ページコンポーネントにmetaってプロパティで定義した算出ゲッターが返す値をdocument.titleに設定できるようになります。

べんり!