这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战
在上一篇笔记中:Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)
我们复习了Vuex的使用方式,以及为什么要使用Vuex,同时看到了Vuex是如何与Vue去结合到一起的。
Vuex是通过Vue插件的方式,通过use函数将Vuex实例对象绑定到Vue中。
然后我们在页面或者组件中可以通过useStore
函数或者this.$store
两种方式在页面中访问到store实例。
首先创建Vuex实例的方式为调用createStore函数来调用
import { createStore } from 'vuex'
export default createStore({
state: { /**...**/ },
getters: { /**...**/ },
actions: { /**...**/ },
mutations: { /**...**/ }
})
createStore函数的源代码,可以看到实际是new了一个Store类的对象
export function createStore (options) {
return new Store(options)
}
Store 构造函数
然后我们看到Store类的源代码,new的第一步首先就是调用constructor构造函数
export class Store {
constructor (options = {}) {
if (__DEV__) {
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `store must be called with the new operator.`)
}
const {
/* 一个数组,包含应用在 store 上的插件方法。*/
plugins = [],
/* 使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/
strict = false,
/* devtool插件状态*/
devtools
} = options
// store internal state -> store的内部状态
/* 用来判断严格模式下是否是用mutation修改state的 */
this._committing = false
/* 存放action */
this._actions = Object.create(null)
/* 存放action订阅器*/
this._actionSubscribers = []
/* 存放mutation */
this._mutations = Object.create(null)
/* 存放getter */
this._wrappedGetters = Object.create(null)
/* module收集器 */
this._modules = new ModuleCollection(options)
/* 根据namespace存放module */
this._modulesNamespaceMap = Object.create(null)
/* 存放订阅者 */
this._subscribers = []
/* 缓存Getter */
this._makeLocalGettersCache = Object.create(null)
// EffectScope instance. when registering new getters, we wrap them inside
// EffectScope so that getters (computed) would not be destroyed on
// component unmount.
// EffectScope 实例。 当注册新的 getter 时,我们将它们包裹在里面
// EffectScope 使 getters (computed) 不会在组件unmount时销毁。
this._scope = null
this._devtools = devtools
// bind commit and dispatch to self
/* 将dispatch与commit调用的this绑定为store对象本身*/
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)
}
// strict mode
/* 严格模式(使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误)*/
this.strict = strict
// 取出state
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
/**
* 初始化根模块
* 递归注册所有子模块
* 并收集 this._wrappedGetters 中的所有模块 getter
*/
installModule(this, state, [], this._modules.root)
// initialize the store state, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化store的状态,为reactive响应式属性
// 还将 _wrappedGetters 注册为computed计算属性
resetStoreState(this, state)
// apply plugins
/* 调用插件 */
plugins.forEach(plugin => plugin(this))
}
//...
}
可以看到Store的构造函数除了初始化一些内部变量以外,主要做了两件事情,一是执行了installModule(初始化module),二是通过resetStoreState(通过Vue3的reactive使store的状态实现“响应式”)。
installModule
installModule的作用主要是为module加上namespace名字空间(如果有)后,注册mutation、action以及getter,同时递归安装所有子module。
export function installModule (store, rootState, path, module, hot) {
// 判断是否是根目录
const isRoot = !path.length
// 是否设置了命名空间
const namespace = store._modules.getNamespace(path)
// register in namespace map
// 在namespace中进行module的存储
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
// 在不是根组件且不是 hot 条件的情况下,通过getNestedState方法拿到该module父级的state,拿到其所在的moduleName
if (!isRoot && !hot) {
/* 获取父级的state */
const parentState = getNestedState(rootState, path.slice(0, -1))
/* module的name */
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('.')}"`
)
}
}
// 将其state设置到父级state对象的moduleName属性中,由此实现该模块的state注册(首次执行这里,因为是根目录注册,所以并不会执行该条件中的方法)
parentState[moduleName] = module.state
})
}
// module上下文环境设置
// 命名空间和根目录条件判断完毕后,接下来定义local变量和module.context的值,执行makeLocalContext方法,为该module设置局部的 dispatch、commit方法以及getters和state(由于namespace的存在需要做兼容处理)。
const local = module.context = makeLocalContext(store, namespace, path)
// 定义local环境后,循环注册我们在options中配置的action以及mutation等。
// 注册对应模块的mutation,供state修改使用
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册对应模块的action,供数据操作、提交mutation等异步操作使用
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注册对应模块的getters,供state读取使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 注册完了根组件的actions、mutations以及getters后,
// 递归调用自身,为子组件注册其state,actions、mutations以及getters等。
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
resetStoreState
执行完成各module的install后,执行resetStoreState方法,进行store组件的初始化。
// initialize the store state, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化store的状态,为reactive响应式属性
// 还将 _wrappedGetters 注册为computed计算属性
resetStoreState(this, state)
我们可以把Vuex理解为Vue的组件,所有配置的state、actions、mutations以及getters都是其组件的属性,所有操作都是围绕着这个组件。下面我们来看其细节:
export function resetStoreState (store, state, hot) {
// 之前的 _state 状态
const oldState = store._state
// 之前的 effectScope
const oldScope = store._scope
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computedObj = {}
const computedCache = {}
// create a new effect scope and create computed object inside it to avoid
// getters (computed) getting destroyed on component unmount.
// 创建一个新的effectScope并在其中创建computed计算属性,
// 以避免组件unmount时 getter(computed)被破坏。
const scope = effectScope(true)
scope.run(() => {
/* 通过Object.defineProperty为每一个getter方法设置get方法,比如获取this.$store.getters.test的时候获取的是Vue的computed计算属性*/
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldState.
// using partial to return function with only arguments preserved in closure environment.
// 使用计算属性的延迟缓存机制
// 直接使用内联函数会导致闭包保留 oldState。
// 使用partial返回函数,仅在闭包环境中保留参数。
computedObj[key] = partial(fn, store)
computedCache[key] = computed(() => computedObj[key]())
Object.defineProperty(store.getters, key, {
get: () => computedCache[key].value,
enumerable: true // for local getters
})
})
})
/* 这里使用Vue3的reactive来将state实现为响应式对象 */
store._state = reactive({
data: state
})
// register the newly created effect scope to the store so that we can
// dispose the effects when this method runs again in the future.
// 将新创建的 effectScope 作用域注册到 store 中,
// 以便我们在以后再次运行该方法时可以处理这些 effects。
store._scope = scope
// enable strict mode for new state
/* 使能严格模式,保证修改store只能通过mutation修改 */
if (store.strict) {
enableStrictMode(store)
}
if (oldState) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
// 在所有订阅的观察者中dispatch更改
// 以强制重新计算热重载的 getter
store._withCommit(() => {
oldState.data = null
})
}
}
// dispose previously registered effect scope if there is one.
// 如果有,则处理之前注册的 effectScope。
if (oldScope) {
oldScope.stop()
}
}
今天我们看了Store的构造函数,和构造函数中的installModule和resetStoreState函数的作用。
installModule函数主要用来处理初始化module,和所有子module的注册。
resetStoreState函数主要用俩处理store的state和getter的响应式初始化。
明天我们接着分析其中的一些函数细节,和组件中的获取state和action函数连起来看。
一起学习更多前端知识,微信搜索【小帅的编程笔记】,每天更新