Vuexにはモジュールという機能があり、コンポーネントが複雑化して大きな一つのstoreにstateを管理するには難しくなった時のために、モジュールという単位で分けることで管理をすることが出来ます。
以下はmoduleの例です。Vuexのドキュメントに載っているものの写しです。
https://vuex.vuejs.org/ja/guide/modules.html
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA` のステート
store.state.b // -> `moduleB` のステート
moduleAとmoduleBはどちらもstateとmutationsを持っており、new Vuex.Store
でインスタンス化したオブジェクトをstore変数に入れています。その時modulesプロパティの中でmoduleAをa、moduleBをbというプロパティにセットしています。
ここで疑問が浮かんだのですが、moduleA内のstateにアクセスする時はstore.state.a
となります。「aの中のstate」を呼んでいるのに「stateの中からaを呼んでいる」ので、直感的にはstore.modules.a.state
ではないかと思いました。しかしstore.state.a
は間違っていません。
理由を探るためVuexのソースを見てみました。結論から言うと、Storeクラス内にモジュール内のstateとは全く別のstateというオブジェクトを作り、モジュール内のstateを代入して置き換えています。
詳しくVuexのコードを見てみます。上のコードのstore
変数はnew Vuex.Store
で作ったオブジェクトなので、Storeクラスの中にstate
オブジェクトがありそうです。
Storeクラスはsrc/store.jsに定義されていました。
https://github.com/vuejs/vuex/blob/dev/src/store.js
stateは以下のように定義されていました。関係のある部分だけまとめています。
import ModuleCollection from './module/module-collection'
export class Store {
constructor(options = {}) {
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
}
}
Storeクラスのstate変数にthis._modules.root.state
が入っています。これが一番上の例でいうstore.state
に該当します。
Storeをインスタンス化する時引数に入ったオブジェクトをoptions
に入れています。このoptionsが一番上の例の{modules: {a: moduleA, b: moduleB}}
の部分となります。
optiosを引数に入れたModuleCollection
クラスを初期化して_modules
に入れ、中のroot.state
を取り出しているようです。
ここで注意するべきはstore.jsの中にmodules
という変数はありません。したがってstore.modules.state.a
と書いてしまうとstore.modulesがundefinedであるためにエラーになります。
ではstore.state
にセットしているroot
は何なのか、moduleAとmoduleBはどのように取り出しているのかさらに掘り下げるためにModuleCollectionクラスを見てみます。こちらも関係ある部分だけを抜粋しました。
https://github.com/vuejs/vuex/blob/dev/src/module/module-collection.js
import Module from './module'
import { forEachValue } from '../util'
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
register (path, rawModule, runtime = true) {
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
ModuleCollection
をnewするときに引数にnew Vuex.Store
の引数と同じものを入れており、それを使ってregister
関数を実行しています。
register関数内でModule
クラスをnewしており、this.root
に代入しています。これが先ほどstore.jsでみたthis._modules.root
となります。
もしmodulesがあれば下の方のif(rawModule.modules)
の中身が通るため、中のオブジェクトループで回して再帰でregister関数を実行しているようです。このrawChildModule
に上の例でいうmoduleA
、moduleB
が入ってきます。
newしているModuleクラスのコンストラクタもみてみます。
https://github.com/vuejs/vuex/blob/dev/src/module/module.js
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
このrawModuleには上の例でいうmoduleA、moduleBが入るため、その中のstateを新たにthis.stateに代入しているようです。これでstore.state
の中に各モジュール内のstateの中身を移すことが出来ました。