この記事はVue.js #3 Advent Calendar 2017 – Qiita 23日目の記事です。
拙作であるFlux Framework、syntagmeのVuePluginを書く機会があり、
それを通じてVueの仕組みの理解を深めたのでそのお話です。
vue-syntagmeはvue-routerの実装を参考にさせていただきました。
環境
- Vue 2.5
構造
プラグインの登録 - install
公式のドキュメントでは如何のように説明されています。
Vue.js プラグインは install メソッドを公開する必要があります。このメソッドは第 1 引数は Vue コンストラクタ、第 2 引数は任意で options が指定されて呼び出されます
今回、syntagmeではこのinstallを使って2つの機能を提供しました。
-
vm.$state
... stateオブジェクトへのアクセサ。 -
vm.$action
... actionCreatorの実行。
mixinの定義
今回行う対応は3つあります。
-
$state
というリアクティブなプロパティの定義 -
$action
というsyntagmeのインスタンスにプロキシするメソッドの定義 - 継承していれば子でも上記機能を利用できるようにする仕組み
これらは、install関数内でmixinを定義します。
export function install (Vue) {
if (install.installed && _Vue) return
install.installed = true
Vue.mixin({
})
}
初期化処理
syntagmeの実行を初期化するため、ルートとなるcomponentではsyntagmeをラップしたVueSyntagmeのインスタンスを初期化します。
Vue.mixin({
beforeCreate () {
this._syntagme = this.$options.syntagme
this._syntagme.init()
}
})
このとき、 this
はrootのvmであり、this.$options.syntagme
はVueSyntagmeのインスタンスを想定しています。
Vue.use(VueSyntagme)
const syntagme = new VueSyntagme(options)
export { syntagme }
こんなイメージで利用します
vm.$state
プロパティの定義
vm.$state
はVueのライフサイクルとは異なるsyntagmeのライフサイクルで更新させます。ただsyntagmeのstateを参照したとしても、そこで起きた更新をVueは検知できません。
そこで初期化時にVue.util.defineReactive
を用いてsyntagmeのstateの更新をVueに伝えられるようにします。
defineReactive
についてはVue.jsでリアクティブな独自データスコープを定義するの記事を参考にさせていただきました。
Vue.mixin({
beforeCreate () {
this._syntagme = this.$options.syntagme
this._syntagme.init()
Vue.util.defineReactive(this, '_state', this._syntagme._state)
}
})
これでstateを参照できるように... おっと、_state
直接見るっていうのはお行儀が良くないですね。参照用のプロパティを定義します。
export default install (Vue) {
Vue.mixin({
beforeCreate () {
this._syntagme = this.$options.syntagme
this._syntagme.init()
Vue.util.defineReactive(this, '_state', this._syntagme._state)
}
})
Object.defineProperty(Vue.prototype, '$state', {
get () { return this._syntagmeRoot._state }
})
}
vm.$action
メソッドの定義
こちらは単純に関数の追加なので、Vueのprototypeに直接機能を追加します。
export default install (Vue) {
Vue.mixin({
beforeCreate () {
this._syntagmeRoot = this
this._syntagme = this.$options.syntagme
this._syntagme.init()
Vue.util.defineReactive(this, '_state', this._syntagme._state)
}
})
Object.defineProperty(Vue.prototype, '$state', {
get () { return this._syntagmeRoot._state }
})
Vue.prototype.$action = function (action_type, args) {
return this._syntagmeRoot._syntagme.action(action_type, args)
}
}
機能を継承する
想定では、Rootでsyntagmeのインスタンスを渡し、それを子のContainerComponentで利用します。
なので、Rootのときは初期化を、それ意外の場合は、親からsyntagmeのプロパティを参照します。
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.syntagme)) {
this._syntagmeRoot = this
this._syntagme = this.$options.syntagme
this._syntagme.init(this)
Vue.util.defineReactive(this, '_state', this._syntagme._state)
} else {
this._syntagmeRoot = (this.$parent && this.$parent._syntagmeRoot)
}
},
})
VueSyntagmeクラス
ここまで度々登場してきた、処理の本体であるところのVueSyntagmeクラスですが、詳細はsyntagmeの実装に依存するので割愛します。
今回プラグインとして公開するaction
, プラグイン内で利用するinit
_state
, そしてスタティックメソッドとして定義されたinstall
を持ちます。
import install from './install'
export default class VueSyntagme {
init () { ... }
action (type, parms) { ... }
actionCreator(type, ac) { ... }
}
VueSyntagme.install = install
VueSyntagme.version = '__VERSION__'
このクラスはインスタンス化され、プラグイン外から呼び出すことも想定しています。
Vue.use
useします。この辺はおなじみですね。
import Vue from 'vue'
import VueSyntagme form 'vue-syntagme'
Vue.use(VueSyntagme)
const syntagme = new VueSyntagme()
syntagme.actionCreator('TAP', () => {
return { action: 'tap' }
})
export {
syntagme,
computed () {
count () { return this.$state.tapCount }
},
methods: {
tap () { this.$action('TAP') }
}
}
まとめ
本記事では、Vueのプラグイン機構を用いて、プロパティ、関数などを実際に登録する手順を示しました。
Vueは公式ドキュメントが充実しており、ほとんどの場合公式ドキュメントで問題が解決するのですが、それはそれとして、すでにVueには多くのプラグインが提供され、軽く目を通すだけでも勉強になります。興味がある方はぜひ読んでみるとよいです。
このように、まあ誰が使うかも定かではありませんが、試しにプラグインを書いてみるとより理解が深まりますのでぜひ一度書いてみるとVueの世界をしるのでおすすめです。
※なお、Vueは公式にFluxフレームワークとしてvuexを推奨しています。僕の場合は事情がある程度特殊なため、自前フレームワークとそれを利用するプラグインを使っています。