本文基于Vuex 4.1.0
版本源码进行剖析
文章内容
应用简略的源码展现Vuex
的用法,并且基于用法中所波及到的源码进行剖析
介绍
上面的介绍摘录于Vuex官网文档,总结起来就是Vuex
是一个具备响应式和肯定规定的全局数据管理对象
Vuex
是一个专为 Vue.js
利用程序开发的状态管理模式 + 库。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化
const Counter = { // 状态 data () { return { count: 0 } }, // 视图 template: ` <div>{{ count }}</div> `, // 操作 methods: { increment () { this.count++ } }}createApp(Counter).mount('#app')
这个状态自治理利用蕴含以下几个局部:
- 状态,驱动利用的数据源;
- 视图,以申明形式将状态映射到视图;
- 操作,响应在视图上的用户输出导致的状态变动。
以下是一个示意“单向数据流”理念的简略示意:
然而,当咱们的利用遇到多个组件共享状态时,单向数据流的简洁性很容易被毁坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为须要变更同一状态。
对于问题一,传参的办法对于多层嵌套的组件将会十分繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,咱们常常会采纳父子组件间接援用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式十分软弱,通常会导致无奈保护的代码。
因而,咱们为什么不把组件的共享状态抽取进去,以一个全局单例模式治理呢?在这种模式下,咱们的组件树形成了一个微小的“视图”,不论在树的哪个地位,任何组件都能获取状态或者触发行为!
通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。
初始化示例
import { createApp } from 'vue'import { createStore } from 'vuex'const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... }}// 创立一个新的 store 实例const store = createStore({ state () { return { count: 0 } }, mutations: { increment (state) { state.count++ } }, modules: { a: moduleA }})// store.state.a // -> moduleA 的状态const app = createApp({ /* 根组件 */ })// 将 store 实例作为插件装置app.use(store)
createStore:创立store
从上面代码能够晓得,能够分为4个局部:
- 第1局部:初始化
dispatch
和commit
办法,对应Vuex
的actions
和mutations
- 第2局部:
installModule()
初始化root module
,解决getters
、mutations
、actions
,而后递归解决子module
- 第3局部:
resetStoreState()
建设getters
和state
的computed
关系,将state
设置为响应式类型,解决oldState
和oldScope
function createStore(options) { return new Store(options)}class Store { constructor(options = {}) { const { plugins = [] } = options // 第1局部 this._modules = new ModuleCollection(options) const store = this const { dispatch, commit } = this this.dispatch = function boundDispatch(type, payload) { return dispatch.call(store, type, payload) } this.commit = function boundCommit(type, payload, options) { return commit.call(store, type, payload, options) } const state = this._modules.root.state // 第2局部 installModule(this, state, [], this._modules.root) // 第3局部 resetStoreState(this, state) // 第4局部 plugins.forEach(plugin => plugin(this)) }}
第1局部:new ModuleCollection(options)
因为应用繁多状态树,利用的所有状态会集中到一个比拟大的对象。当利用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 容许咱们将 store 宰割成模块(module)。每个模块领有本人的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样形式的宰割
从上面的代码能够晓得,new ModuleCollection(options)
会进行rawRootModule
的new Module()
初始化,并且赋值给this.root
,而后遍历rawRootModul.modules
,对每一个module
都进行new Module()
的初始化和parent-child
的关系建设
path
是一个数组,每一层都是数组的一个元素,应用这个数组path
能够找到对应的下级parent
runtime=true
代表该module
是否是运行时创立的module
class ModuleCollection { constructor(rawRootModule) { // rawRootModule代表createStore时传入的对象 // 比方{state:{}, mutations:{}, modules: {}} this.register([], rawRootModule, false) } register(path, rawModule, runtime = true) { const newModule = new Module(rawModule, runtime) if (path.length === 0) { this.root = newModule } else { // 如果有嵌套多层,每一层都是数组的一个item // parent是目前rawModule的间接下级 const parent = this.get(path.slice(0, -1)) parent.addChild(path[path.length - 1], newModule) } if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { // key=注册的Module的名称 this.register(path.concat(key), rawChildModule, runtime) }) } }}
new Module(rawModule, runtime)
如上面代码块所示,只是将目前的rawModule
的属性拆解进去造成this.state
以及创立多个辅助办法addChild()
等等
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) || {} } addChild(key, module) { this._children[key] = module }}
第2局部:installModule
从上面代码块能够晓得,次要执行了
getNamespace()
:从root module
开始,应用path.reduce()
从root->child->目前的module
,检测是否配置namespaced=true
,如果是则拼接它们对应的命名,也就是它们注册的store.modules[A]=moduleA
的A
这个key
_modulesNamespaceMap
:缓存该module
,为mapActions
等语法糖应用,上面会具体分析parentState[moduleName]=module.state
:通过getNestedState()
从root
开始寻找到目前module
的parent state
,通过path[path.length - 1]
拿到目前module
的名称,而后建设父子state
之间的分割makeLocalContext()
:创立一个上下文local
,具备dispatch
、commit
、getters
、state
等属性registerMutation()
:遍历module.mutations
属性定义的所有办法,进行registerMutation()
注册,具体分析请看上面registerAction()
:遍历module.actions
属性定义的所有办法,进行registerAction()
注册,具体分析请看上面registerAction()
:遍历module.getters
属性定义的所有办法,进行registerGetter()
注册,具体分析请看上面child-installModule()
:递归调用installModule()
办法,反复下面步骤进行子module
的解决,具体分析请看上面
总结起来,就是先建设每一个state
之间的关系,而后开始解决以后state
的mutation
、action
、getters
,而后再解决以后state
的children
(解决子module
的mutation
、action
、getters
)
function installModule(store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // 拼接它后面parent所有命名空间 // register in namespace map if (module.namespaced) { 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(() => { parentState[moduleName] = module.state }) } const local = module.context = makeLocalContext(store, namespace, path) module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) // 递归装置子模块 module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })}
makeLocalContext创立以后module的辅助办法,为主动传参做筹备
传递参数
store:最外层的store,包含state、getters、mutations、actions、modules的Object对象
namespace:拼接目前该module
后面parent所有命名空间的字符串,比方root/child1/child2
path:每一层的数组汇合,每一个Item就是一层
local.dispatch
如果noNamespace
为空,则间接应用store.dispatch
,如果有命名空间,则应用一个新的办法,咱们要留神,这里传入的_type
就是一个办法名称,而后咱们会应用type = namespace + type
拼接命名空间
在上面的剖析中咱们会晓得,这个local变量其实是作为内部注册子Module的actions
办法时能够传入的子Module的dispatch,换句话说,所有数据都寄存在根State
中,咱们之所以在内部能够间接应用子Module的dispatch对象+办法名,外部是映射到根State[命名空间+办法名]
而已
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ dispatch, commit, getters, rootGetters }) { // 这里的dispatch就是子Module的dispatch,能够间接dispatch("incrementIfOddOnRootSum") // rootState还要加上命名空间,比方rootState.dispatch("a/incrementIfOddOnRootSum") } }}
local.commit
如果noNamespace
为空,则间接应用store.commit
,如果有命名空间,则应用一个新的办法,咱们要留神,这里传入的_type
就是一个办法名称,而后咱们会应用type = namespace + type
拼接命名空间
在上面的剖析中咱们会晓得,这个local变量其实是作为内部注册子Module的mutations
办法时能够传入的子Module的dispatch,换句话说,所有数据都寄存在根State
中,咱们之所以在内部能够间接应用子Module的dispatch对象+办法名,外部是映射到根State[命名空间+办法名]
而已,具体例子相似下面的local.dispatch
function makeLocalContext(store, namespace, path) { const noNamespace = namespace === '' const local = { dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type; } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type; } store.commit(type, payload, options) } } // getters and state object must be gotten lazily // because they will be changed by state update Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { // getNestedState=path.reduce((state, key) => state[key], state) get: () => getNestedState(store.state, path) } }) return local}
local.getters拿到以后子Module的getters
如果noNamespace
为空,则间接应用store.getters
,如果有命名空间,则触发makeLocalGetters()
,建设代理Object.defineProperty
进行get()
办法的映射,实质也是应用store.getters[type]
,比方store.getters["a/a_modules"]
获取到子Module
的getters
function makeLocalGetters(store, namespace) { if (!store._makeLocalGettersCache[namespace]) { const gettersProxy = {}; const splitPos = namespace.length; Object.keys(store.getters).forEach(type => { // type.slice(0, splitPos)="a/a_modules/" // namespace="a/a_modules/" if (type.slice(0, splitPos) !== namespace) return; // localType="moduleA_child_getters1" const localType = type.slice(splitPos); // gettersProxy["moduleA_child_getters1"]=store.getters["a/a_modules/moduleA_child_getters1"] Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }); }); store._makeLocalGettersCache[namespace] = gettersProxy; } return store._makeLocalGettersCache[namespace];}
那为什么要这样代理呢?
如上面具体实例所示,咱们晓得,一个子Module内部调用注册的getters对象中的办法是能够拿到currentGetters对象的
上面代码块中的moduleA_child_getters1()
办法传入参数currentGetters
就是以后子Module的getters对象,它同时也能够拿到currentGetters.moduleA_child_getter2
办法,而外部中,是通过下面代码块中的Object.defineProperty
代理拿到store.getters[命名空间+办法名]
换句话说,所有数据都寄存在根State
中,咱们之所以在内部能够间接应用子Module的getters对象+办法名,外部是映射到根State[命名空间+办法名]
而已
const moduleA_child = { namespaced: true, state() { return { id: "moduleA_child", count: 11, todos: [ {id: 1, text: '...', done: true}, {id: 2, text: '...', done: false} ] } }, getters: { moduleA_child_getters1(state, currentGetters, rootState, rootGetters) { return state.todos.filter(todo => todo.done); }, moduleA_child_getter2(){} }}
local.state拿到以后子Module的state
如果有多层级的命名空间,比方root/child1/child2
,我传递一个"child2"
,getNestedState()
能够主动从root
遍历到目前module
拿到目前module
所持有的state
如下面图片所示,path
=["a", "a_modules"]
,所以getNestedState()
=rootState["a"]["a_modules"]
拿到子Module
的state
function getNestedState (state, path) { return path.reduce(function (state, key) { return state[key]; }, state)}
registerGetter: 内部调用getters
function installModule(store, rootState, path, module, hot) { //... module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) //...}
registerGetter
的type
是拼接它后面parent
所有命名空间,实质上是应用store._wrappedGetters[命名空间]
=目前module
注册的getters
办法
比方上面的具体实例,一共会执行两次registerGetter()
第一次:"a/"
+"moduleA_getters1"
第二次:"a/a_modules/"
+"moduleA_child_getters1"
const moduleA_child = { namespaced: true, getters: { moduleA_child_getters1(state, getters, rootState, rootGetters) { return state.todos.filter(todo => todo.done); } }}const moduleA = { namespaced: true, getters: { moduleA_getters1(state, getters, rootState, rootGetters) { return state.todos.filter(todo => todo.done); } }, modules: { a_modules: moduleA_child }}const store = createStore({ modules: { a: moduleA }});
registerGetter()
传入的参数rawGetter
,就是下面示例中内部调用的moduleA_child_getters1()
办法
由上面第3局部resetStoreState的剖析能够晓得,最终内部调用store.getters.xxx
实质就是调用store._wrappedGetters.xxx
,具体的逻辑放在上面第3局部resetStoreState进行剖析,这里只有把握为什么local
可能拿到以后子module
的state
和getters
即可
rawGetter
传入参数为:
- 一开始创立的上下文
local.state
拿到的目前子module
的state
- 一开始创立的上下文
local.getters
拿到的目前子module
的getters
- 传入原始根对象
store
的state
- 传入原始根对象
store
的getters
function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { return } // type="a/a_modules/moduleA_child_getters1" store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }}
registerMutation: 内部调用commit
type
是拼接它后面parent
所有命名空间,实质上是应用store._mutations[type]
=[]
,而后将目前module.mutations
属性注册的办法存入该数组中,其中传入参数为
- 传入原始根对象
store
- 一开始创立的上下文
local.state
拿到的目前子module
的state
- 内部调用时传入的数据
function registerMutation (store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload) })}
registerAction: 内部调用dispatch
type
是拼接它后面parent
所有命名空间,实质上是应用store._actions[type]
=[]
,而后将目前module._actions
属性注册的办法存入该数组中,其中传入参数为
- 内部调用时传入的数据
而后将返回后果包裹成为一个Promise
数据
function registerAction (store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []) entry.push(function wrappedActionHandler (payload) { let res = handler.call(store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload) if (!isPromise(res)) { res = Promise.resolve(res) } if (store._devtoolHook) { return res.catch(err => { store._devtoolHook.emit('vuex:error', err) throw err }) } else { return res } })}
小结
创立的上下文local
代表了以后子module
,在创立过程中解决的getters
、mutations
、actions
是为了内部调用时可能主动传入该参数,比方上面代码块中getters
传入的参数(state, getters, rootState, rootGetters)
,在内部调用时不必特意传入这些参数,最终会主动传入,实质就是创立的上下文local
的功绩
const foo = { namespaced: true, // ... getters: { someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter' }, }}
第3局部:resetStoreState
步骤1: 内部调用state.getters映射到外部_wrappedGetters
当内部调用store.getters[xxx]
时,从上面代码能够晓得,会触发computedCache[key].value
因为computedCache
与computedObj
建设了computed
计算关系,因而触发computedCache[key].value
等于触发computedObj[key]()
触发computedObj[key]()
,实质就是触发fn(store)
,也就是_wrappedGetters[key]
function resetStoreState(store, state, hot) { // 步骤1: // 建设getters和wrappedGetters的computed关系 // 实际上是建设getters和state的computed关系 scope.run(() => { forEachValue(wrappedGetters, (fn, key) => { // key=命名空间+办法名 computedObj[key] = partial(fn, store) computedCache[key] = computed(() => computedObj[key]()) Object.defineProperty(store.getters, key, { get: () => computedCache[key].value, enumerable: true // for local getters }) }) })}function partial (fn, arg) { // arg = store return function () { return fn(arg) }}
在下面第2局部的registerGetter
(如上面的代码块所示),咱们wrappedGetters[key]
理论就是某一个命名空间为key
(比方root/child1/child2
)的getters
办法,传入参数为那个module.state
、module.state
、root.state
、root.getters
function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { return } // type=命名空间+办法名,比方type="a/a_modules/moduleA_child_getters1" store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }}
传入参数对应内部调用的代码如下代码块所示
state
: 对应local.state
getters
: 对应local.getters
rootState
: 对应store.state
rootGetters
: 对应store.getters
const foo = { namespaced: true, // ... getters: { someGetter (state, getters, rootState, rootGetters) { // 以后子module的getters,不必带命名空间 getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' // root的getters,须要带命名空间 rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter' }, }}
步骤2: 将state转化为reactive
间接利用Vue3 reactive
将state
转化为响应式数据,并且Store.js
中提供获取state
的形式为获取this.state.data
数据
function resetStoreState(store, state, hot) { // 步骤1: // 建设getters和wrappedGetters的computed关系 // 实际上是建设getters和state的computed关系 // 步骤2: 将state转化为reactive store._state = reactive({ data: state })}class Store { get state () { return this._state.data }}
步骤3: 解决oldState和oldScope
function resetStoreState(store, state, hot) { // 步骤1: // 建设getters和wrappedGetters的computed关系 // 实际上是建设getters和state的computed关系 // 步骤2: 将state转化为reactive // 步骤3: 解决oldState和oldScope const oldState = store._state const oldScope = store._scope if (oldState) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { oldState.data = null }) } } // dispose previously registered effect scope if there is one. if (oldScope) { oldScope.stop() }}
app.use(store):Vuex作为插件装置到Vue中
use(plugin, ...options) { if (plugin && isFunction(plugin.install)) { installedPlugins.add(plugin); plugin.install(app, ...options); } else if (isFunction(plugin)) { installedPlugins.add(plugin); plugin(app, ...options); } return app;}
从下面Vue3
的源码能够晓得,最终会触发Vuex
的install()
办法
class Store { install (app, injectKey) { app.provide(injectKey || storeKey, this) app.config.globalProperties.$store = this //...省略_devtools相干代码 }}
从下面代码能够晓得,store
是采纳provide
的办法装置到Vue
中,因而当初咱们能够晓得,为什么我能够应用useStore()
在Vue3 setup()
中间接获取到store
对象的(如上面代码块所示,应用inject
获取store
对象)
// src/injectKey.jsexport const storeKey = 'store'export function useStore (key = null) { return inject(key !== null ? key : storeKey)}
而因为app.config.globalProperties.$store = this
,因而咱们在Options API
也能够通过this.$store
获取到state
对象,比方上面图片所示
那为什么app.config.globalProperties.$store = this
的时候,咱们就能应用this.$store
获取到state
对象呢?
在咱们以前的文章《Vue3源码-整体渲染流程浅析》中,咱们在解说setupStatefulComponent()
时有说到,instance.ctx
进行了new Proxy()
的拦挡
function setupStatefulComponent(instance, isSSR) { const Component = instance.type; //...省略一些简略的校验逻辑 // 0. 创立渲染代理属性拜访缓存 instance.accessCache = Object.create(null); // 1. 创立public实例 / 渲染代理,并且给它Object.defineProperty一个__v_skip属性 // def(value, "__v_skip" /* SKIP */, true); instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)); const { setup } = Component; if (setup) { // 2. 解决setup()函数 // ...进行一系列解决 } else { // 3. 没有setup函数时,间接调用finishComponentSetup finishComponentSetup(instance, isSSR); }}const PublicInstanceProxyHandlers = { get({ _: instance }, key) {}, set({ _: instance }, key, value) {}, has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {}};
而this.$store
会触发get()
办法的查找,查找过程十分繁冗,最终命中appContext.config.globalProperties
,从全局数据中查找是否有key=$store
,因而app.config.globalProperties.$store = this
的时候,咱们就能应用this.$store
获取到app.config.globalProperties.$store
,从而获取到state
对象
get({ _: instance }, key) { const { ctx, setupState, data, props, accessCache, type, appContext } = instance; //...省略很多其它查找过程 const publicGetter = publicPropertiesMap[key]; if (publicGetter) { // ... } else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) { //... } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { // ... } else if (((globalProperties = appContext.config.globalProperties), hasOwn(globalProperties, key)) { return globalProperties[key]; }}
动静注册和卸载Module
registerModule
从上面代码能够晓得,动静注册module
时,会先实例化该模块,进行new Module()
,而后递归实例化它的子modules
而后触发installModule()
和resetStoreState
,实质跟createStore()
创立store
时的步骤统一,先获取命名空间组成的地址字符串,而后解决getters
、mutations
、actions
跟命名空间地址的联合,最初进行state
数据的响应式初始化和旧的状态的革除
// this._modules = new ModuleCollection(options)registerModule(path, rawModule, options = {}) { if (typeof path === 'string') path = [path] this._modules.register(path, rawModule) installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... resetStoreState(this, this.state)}class ModuleCollection { register(path, rawModule, runtime = true) { 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) }) } }}
unregisterModule
unregisterModule (path) { if (typeof path === 'string') path = [path] this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) delete parentState[path[path.length - 1]] }) resetStore(this)}
从下面代码能够晓得,流程次要为:
- 移除
ModuleCollection
的数据 - 应用
_withCommit
移除该state
数据 - 最终重置整个
store
,从新初始化一遍
移除ModuleCollection
的数据
unregister (path) { const parent = this.get(path.slice(0, -1)) const key = path[path.length - 1] const child = parent.getChild(key) if (!child) { return } if (!child.runtime) { return } parent.removeChild(key)}
应用_withCommit
移除该state
数据
_withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing}
最终重置整个store
,从新初始化一遍
function resetStore (store, hot) { store._actions = Object.create(null) store._mutations = Object.create(null) store._wrappedGetters = Object.create(null) store._modulesNamespaceMap = Object.create(null) const state = store.state // init all modules installModule(store, state, [], store._modules.root, true) // reset state resetStoreState(store, state, hot)}
store.commit(): mutations同步更新数据
内部调用代码示例
const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } }})store.commit('increment')
源码剖析
依据type
从this._mutations
拿到对应的数组办法,这部分数组办法的初始化是在下面第2局部installModule()
的registerMutation
初始化的
最初触发_subscribers
订阅函数的执行
比方Store.js
提供了订阅办法,能够给一些插件应用,比方Vuex
内置的Logger
插件,会应用store.subscribe
进行订阅,而后追踪state
的变动
commit(_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { return } this._withCommit(() => { entry.forEach(function commitIterator(handler) { handler(payload) }) }) this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .forEach(sub => sub(mutation, this.state))}
_withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing}
store.commit(): actions异步更新数据
内部调用代码示例
store.dispatch('increment')
源码剖析
跟下面mutaions
执行的代码逻辑大同小异,也是从this._actions
中拿到对应的数组办法,这部分数组办法的初始化是在下面第2局部installModule()
的registerAction
初始化的,不同点在于返回的后果是一个Promise
,并且Vuex
为咱们解决所有可能产生的谬误
同样也会触发_subscribers
订阅函数的执行,这里有sub.before
和sub.after
dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] if (!entry) { return } try { this._actionSubscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe .filter(sub => sub.before) .forEach(sub => sub.before(action, this.state)) } catch (e) {} const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) {} resolve(res) }, error => { reject(error) }) }) }
语法糖mapXXX
内部调用代码示例
computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }), ...mapGetters('some/nested/module', [ 'someGetter', // -> this.someGetter 'someOtherGetter', // -> this.someOtherGetter ])},methods: { ...mapActions('some/nested/module', [ 'foo', // -> this.foo() 'bar' // -> this.bar() ])}
mapState
从上面代码块能够晓得,传入namespace
能够利用getModuleByNamespace()
办法疾速找到对应的module
对象,而后返回对应module
对应的state[val]
,最终拿到的res
是一个Object
对象
const mapState = normalizeNamespace((namespace, states) => { const res = {} normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState () { let state = this.$store.state let getters = this.$store.getters if (namespace) { const module = getModuleByNamespace(this.$store, 'mapState', namespace) if (!module) { return } state = module.context.state getters = module.context.getters } return typeof val === 'function' ? val.call(this, state, getters) : state[val] } // mark vuex getter for devtools res[key].vuex = true }) return res})
function normalizeMap (map) { if (!isValidMap(map)) { return [] } return Array.isArray(map) ? map.map(key => ({ key, val: key })) : Object.keys(map).map(key => ({ key, val: map[key] }))}
mapState
整体流程能够概括为:
利用getModuleByNamespace()
从初始化的_modulesNamespaceMap
拿到指定的Module.js
对象,而module.context
是在installModule()
时建设的上下文,它进行了该module
对应getters
、mutations
、actions
等属性的解决(初始化时进行了命名空间的解决),可能间接通过该module.context.state
间接通过key
拿到对应的数据
//mapState调用function getModuleByNamespace (store, helper, namespace) { const module = store._modulesNamespaceMap[namespace] return module}// createStore初始化时调用function installModule() { const local = module.context = makeLocalContext(store, namespace, path)}export function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module }}
因而mapState
返回的对象为
{ a: this.$store.state.some.nested.module.a, b: this.$store.state.some.nested.module.b}
mapMutations
、mapGetters
、mapActions
等逻辑跟mapState
简直一样,这里不再反复剖析
Vue系列其它文章
- Vue2源码-响应式原理浅析
- Vue2源码-整体流程浅析
- Vue2源码-双端比拟diff算法 patchVNode流程浅析
- Vue3源码-响应式零碎-依赖收集和派发更新流程浅析
- Vue3源码-响应式零碎-Object、Array数据响应式总结
- Vue3源码-响应式零碎-Set、Map数据响应式总结
- Vue3源码-响应式零碎-ref、shallow、readonly相干浅析
- Vue3源码-整体流程浅析
- Vue3源码-diff算法-patchKeyChildren流程浅析
- Vue3相干源码-Vue Router源码解析(一)
- Vue3相干源码-Vue Router源码解析(二)