本文基于
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.js
export 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 源码解析(二)