exportclassStore{ constructor(options = {}) { // Auto install if it is not done yet and `window` has `Vue`. // To allow users to avoid auto-installation in some cases, // this code should be placed here. See #731 if (!Vue && typeofwindow !== 'undefined' && window.Vue) { install(window.Vue) }
if (__DEV__) { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert( typeofPromise !== 'undefined', `vuex requires a Promise polyfill in this browser.` ) assert( thisinstanceof Store, `store must be called with the new operator.` ) }
const { plugins = [], strict = false } = options
// store internal state this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self const store = this const { dispatch, commit } = this this.dispatch = functionboundDispatch(type, payload) { return dispatch.call(store, type, payload) } this.commit = functionboundCommit(type, payload, options) { return commit.call(store, type, payload, options) }
// strict mode this.strict = strict
const state = this._modules.root.state
// init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state)
exportclassStore{ constructor(options = {}) { // Auto install if it is not done yet and `window` has `Vue`. // To allow users to avoid auto-installation in some cases, // this code should be placed here. See #731 if (!Vue && typeofwindow !== 'undefined' && window.Vue) { install(window.Vue) } } }
exportfunctioninstall(_Vue) { if (Vue && _Vue === Vue) { if (__DEV__) { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin(Vue) }
new Store 的时候如果还未安装,并且已经有全局引入 Vue 的情况下,就会自动安装,但如果已经安装,则无需再次安装。
其实就是只做了一件事:将 store 挂载到 $store ,所以我们可以在 vue 组件中通过 $store 访问到 store。
2. 异常检测
如果是在开发环境的话,就会做一些检查:
1 2 3 4 5 6 7 8
if (__DEV__) { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert( typeofPromise !== 'undefined', `vuex requires a Promise polyfill in this browser.` ) assert(thisinstanceof Store, `store must be called with the new operator.`) }
// init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root)
// register in namespace map if (module.namespaced) { if (store._modulesNamespaceMap[namespace] && __DEV__) { console.error( `[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join( '/' )}` ) } store._modulesNamespaceMap[namespace] = module }
// set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { if (__DEV__) { if (moduleName in parentState) { console.warn( `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join( '.' )}"` ) } } Vue.set(parentState, moduleName, module.state) }) }
const local = (module.context = makeLocalContext(store, namespace, path))
// set state if (!isRoot && !hot) { var parentState = getNestedState(rootState, path.slice(0, -1)) var moduleName = path[path.length - 1] store._withCommit(function () { if (process.env.NODE_ENV !== 'production') { if (moduleName in parentState) { console.warn( '[vuex] state field "' + moduleName + '" was overridden by a module with the same name at "' + path.join('.') + '"' ) } } Vue.set(parentState, moduleName, module.state) }) }
这里主要是将子模块的 state 设置到父模块的 state 中去,这也是为什么我们可以通过这种方式来获取子模块的 state :
初始化完 module 以后就会处理 state 里的数据,使它变成响应式,同时处理前面经过包装的 getter ,使它变成类似于 computed 意义的东西:
1 2 3
// initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state)
// bind store public getters store.getters = {} // reset local getters cache store._makeLocalGettersCache = Object.create(null) const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldVm. // using partial to return function with only arguments preserved in closure environment. computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true// for local getters }) })
// use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent
// enable strict mode for new vm if (store.strict) { enableStrictMode(store) }
if (oldVm) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { oldVm._data.$$state = null }) } Vue.nextTick(() => oldVm.$destroy()) } }
// silent 是取消 Vue 所有的日志与警告 // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent
5.2. 处理 getters
而 getter 的本质就是一个 computed :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// bind store public getters store.getters = {} // reset local getters cache store._makeLocalGettersCache = Object.create(null) const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldVm. // using partial to return function with only arguments preserved in closure environment. computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true// for local getters }) })
exportconst mapState = normalizeNamespace((namespace, states) => { const res = {} if (__DEV__ && !isValidMap(states)) { console.error( '[vuex] mapState: mapper parameter must be either an Array or an Object' ) } normalizeMap(states).forEach(({ key, val }) => { res[key] = functionmappedState() { let state = this.$store.state let getters = this.$store.getters if (namespace) { constmodule = getModuleByNamespace(this.$store, 'mapState', namespace) if (!module) { return } state = module.context.state getters = module.context.getters } returntypeof val === 'function' ? val.call(this, state, getters) : state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res })
其核心原理就是将传入的 states 进行序列化,然后在当前命名空间对应的模块中获取到这些值,其中还要判断一下是否为函数,是的话则调用该函数并且传入当前模块中的 state 和 getters ,将函数的返回存入对象中,最后返回。
🥳 加载 Disqus 评论