本文基于Vuex 4.1.0
版本源码进行分析
文章内容
使用简单的源码展示Vuex
的用法,并且基于用法中所涉及到的源码进行分析
介绍
下面的介绍摘录于Vuex官方文档,总结起来就是Vuex
是一个具备响应式和一定规则的全局数据管理对象
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
const Counter = {
// 状态
data () {
return {
count: 0
}
},
// 视图
template: `
{{ count }}
`,
// 操作
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
几乎一样,这里不再重复分析