vuex源码解析
首先我们先观察一下官方store实例的结构,然后再考虑按照它的结构实现一下

主要实现一个Store类和ModuleCollect类、以及helper辅助函数
Store类
// 1、插件:挂载$store
// 2、实现Store
// state的name与module名重复,state同名状态会被覆盖掉
import ModuleCollection from './ModuleCollection'
let Vue;
class Store {
constructor(options) {
// 所有的actions,包括子模块的actions
// {string:Array<function>}
this._mutations = {}
// 所有的actions,包括子模块的actions
// {string:Array<function>}
this._actions = {}
// 收集getters的初始容器,还没有实现响应式
// {string:function}
this._wrappedGetters = {}
// 命名空间与module的映射
// {string:module类}
this._modulesNamespaceMap = {}
// actions的插件订阅者
this._actionSubscribers = {}
// mutations的插件订阅者
this._subscribes = []
// getters
this.getters = {} // 需要借助vue的计算属性
// 把数据格式化成一个想要的树结构
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
// 递归将结果进行分类
// this 整个store
// this.state 当前的根状态
// [] 为了递归来创建
// this.module.root 从根模块进行安装
installModule(this, state, [], this._modules.root)
// 将state变为响应式,并且把getters变为计算属性
resetStoreVm(this, state)
// 处理插件
options.plugins.forEach(plugin => plugin(this))
}
// 插件订阅mutations
subscribe(fn) {
this._subscribes.push(fn)
}
// 插件订阅actions
subscribeActions(fn) {
this._actionSubscribers.push(fn)
}
// 提交更改 会在当前的store上找到对应的函数执行
// 采用箭头函数就不需要绑定this了
commit = (mutationName, payload) => {
this._mutations[mutationName].forEach(fn => fn(payload))
}
// dispatch对应的action在当前的store上找到对应的函数执行
// 采用箭头函数就不需要绑定this了
dispatch = (actionName, payload) => {
this._actions[actionName].forEach(fn => fn(payload))
}
get state() {
return this._vm._data.$$state
}
set state(val) {
// 禁止用户直接改变state
throw new Error('不允许改变,请使用replaceState')
}
replaceState(state) {
this._vm._data.$$state = state
}
}
const install = (_Vue) => {
Vue = _Vue
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
},
})
}
// 迭代对象,会将对象的key与value拿到
function forEach(obj, cb) {
Object.keys(obj).forEach(key => {
cb(key, obj[key])
})
}
// 判断是否是对象
function isObject(obj) {
return obj !== null && typeof obj == 'object'
}
// 统一参数
function unifyObjectStyle(type, payload, options) {
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
return {
type,
payload,
options
}
}
// 获取嵌套的子模块的state
function getNestedState(store, path) {
return path.reduce((root, current) => {
return root[current]
}, store.state)
}
// 创造模块自己的getters
function makeLocalGetters(store, namespace) {
let splitPos = namespace.length
const gettersProxy = {}
Object.keys(store.getters).forEach(type => {
if (type.slice(0, splitPos) !== namespace) return
const localType = type.slice(splitPos)
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
return gettersProxy
}
// 初始化store的vm,利用vue实现数据的响应式
// 同时利用vue的计算属性,实现getters的计算属性
function resetStoreVm(store, state) {
let computed = {}
forEach(store._wrappedGetters, (k, fn) => {
computed[k] = fn
// 懒加载,只有获取的时候才会调用get的到getters的值
Object.defineProperty(store.getters, k, {
get: () => store._vm[k],
enumerable: true
})
})
store._vm = new Vue({
data() {
return {
// 利用class的get state,从而实现能通过this.state直接访问
// 同时,利用set state,禁止用户改变state的值
// 使用store自带的replaceState来改变值
$$state: state
}
},
computed
})
}
// 创造子模块的context上下文,主要包括commit、dispatch、state、getters
// 当有命名空间时,commit、dispatch会被默认加上当前的命名空间
// state、getters为子模块内部的state、getters
const makeLocalContext = (store, namespace, path) => {
const noNamespace = namespace === ''
const local = {
// 如果没有命名空间那么子模块的commit直接使用store上的即可
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 如果root为true,表示用户想调用全局的mutations,所以只有当root为false才会添加命名空间
if (!options || options.root) {
type = namespace + type
}
return store.commit(type, payload, options)
},
// 如果没有命名空间那么子模块的dispatch直接使用store上的即可
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 如果root为true,表示用户想调用全局的mutations,所以只有当root为false才会添加命名空间
if (!options || options.root) {
type = namespace + type
}
return store.dispatch(type, payload)
},
}
Object.defineProperties(local, {
getters: {
get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace),
enumerable: true
},
state: {
get: noNamespace ? () => store.state : () => getNestedState(store, path),
enumerable: true
},
})
return local
}
// 安装模块,并且递归安装所有的子模块
function installModule(store, rootState, path, rootModule) {
// 处理子模块的state
if (path.length > 0) {
// 子模块
let parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// vue 不能再对象上增加上不存在的属性 否则不会导致视图的更新
console.log(parent, rootState, 'parent');
Vue.set(parent, path[path.length - 1], rootModule.state || {})
}
// 处理命名空间
let module = store._modules.root
let namespace = path.reduce((space, current) => {
module = module._children[current]
return space + (module.namespaced ? current + '/' : '')
}, '')
// 缓存命名空间对应模块方便后面直接通过命名空间寻找模块
// 后面helper函数会用到这个缓存
store._modulesNamespaceMap[namespace] = rootModule
// 处理context,并绑定到模块上面
let local = makeLocalContext(store, namespace, path)
rootModule.context = local
// 以下代码都是在处理 模块中的getters actions mutations
// 让子模块中的 getters actions mutations 挂载到外层
let getters = rootModule._rawModule.getters
if (getters) {
forEach(getters, (type, fn) => {
store._wrappedGetters[namespace + type] = function () {
return fn(local.state, local.getters, store.state, store.getters)
}
})
}
let mutations = rootModule._rawModule.mutations
if (mutations) {
forEach(mutations, (mutationsName, fn) => {
let mutations = store._mutations[namespace + mutationsName] || []
mutations.push((payload) => {
fn(local.state, payload)
//
store._subscribes.forEach(fn => fn({ type: mutationsName, payload }, rootState))
})
// 发布 让所有的订阅mutations的插件callback依次执行
store._mutations[namespace + mutationsName] = mutations
})
}
let actions = rootModule._rawModule.actions
if (actions) {
forEach(actions, (actionsName, fn) => {
let actions = store._actions[namespace + actionsName] || []
actions.push((payload) => {
fn({
...local,
rootState: store.state,
rootGetters: store.getters
}, payload)
// 发布 让所有的订阅mutations的插件callback依次执行
store._mutations[namespace + actionsName] = mutations
})
store._actions[namespace + actionsName] = actions
})
}
// 递归安装子模块
forEach(rootModule._children, (moduleName, module) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
export default { install, Store }
ModuleCollect类
export default class ModuleCollection {
constructor(options) {
// 注册模块,将模块注册成树结构
this.register([], options)
}
register(path, rootModule) {
const isRoot = path.length === 0
// 将模块格式化
let module = {
_rawModule: rootModule,
_children: {},
state: rootModule.state,
namespaced: rootModule.namespaced
}
if (isRoot) {
// 如果是根模块,将这个进行实例挂载到根节点上
this.root = module
} else {
let parent = path.slice(0, -1).reduce((root, current) => {
return root._children[current]
}, this.root)
parent._children[path[path.length - 1]] = module
}
// 看当前模块是否有modules,开始重新注册
if (rootModule.modules) {
Object.keys(rootModule.modules).forEach(moduleName => {
const childModule = rootModule.modules[moduleName]
this.register(path.concat(moduleName), childModule)
})
}
}
}
helper辅助函数(mapState、mapMutations、mapActions、mapGetters)
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}
// 验证是否是数组或者对象
function isValidMap(map) {
return Array.isArray(map) || isObject(map)
}
// 统一map参数对象为{key,val}形式
function normalizeMap(map) {
if (!isValidMap(map)) {
throw new Error('不是有效的参数')
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
// 统一命名空间
function normalizeNamespace(fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
// 通过命名空间
function getModuleByNamespace(store, helper, namespace) {
const module = store._modulesNamespaceMap[namespace]
if (!module) {
throw new Error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
// mapState函数
export 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) {
// 如果有命名空间,则把state与getters替换对应子模块的state和getters
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]
}
})
return res
})
// mapGetters函数
export const mapGetters = normalizeNamespace((namespace,map) => {
const res = {}
normalizeMap(map).forEach(({key,val}) => {
// 拼接上命名空间前缀
val = namespace + val
res[key] = function mappedGetters() {
return this.$store.getters[val]
}
})
return res
})
// mapMutations函数
export const mapMutations = normalizeNamespace((namespace,map) => {
const res = {}
normalizeMap(map).forEach(({key,val}) => {
res[key] = function mappedMutations(...args) {
let commit = this.$store.commit
if(namespace) {
// 如果有命名空间,则把commit换成子模块的commit
// commit内部,添加了命名空间前缀,所以此处不需要再添加命名空间
let module = getModuleByNamespace(this.$store,'mapMutations',namespace)
if(!module) return
commit = module.context.commit
}
// 需要判断用户传过来的是函数,还是字符串类型
// 需要注意this的指向问题
typeof val === 'function'
? val.apply(this,[commit].concat(args))
: commit.apply(this.$store,[val].concat(args))
}
})
return res
})
// mapActions函数
export const mapActions = normalizeNamespace((namespace,map) => {
const res = {}
normalizeMap(map).forEach(({key,val}) => {
res[key] = function mappedMutations(...args) {
let dispatch = this.$store.dispatch
if(namespace) {
// 如果有命名空间,则把commit换成子模块的commit
// dispatch内部,添加了命名空间前缀,所以此处不需要再添加命名空间
let module = getModuleByNamespace(this.$store,'mapMutations',namespace)
if(!module) return
dispatch = module.context.dispatch
}
// 需要判断用户传过来的是函数,还是字符串类型
// 需要注意this的指向问题
typeof val === 'function'
? val.apply(this,[dispatch].concat(args))
: dispatch.apply(this.$store,[val].concat(args))
}
})
return res
})
// 创建命名空间辅助函数
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})
注意事项
vuex
里面mutations
与actions
可以重名,当重名时,按照注册先后顺序执行- 当state对象名与模块名重复时,模块有更高的优先级,会覆盖掉state里面的同名属性
- getters不能重名,重名会报错。
- store里面的mutations与actions结构为
{string:[]}
,所以能过重名 - store里面的getters结构为
{string:getter}
所以不能重名 - 添加命名空间后,getters、mutations、actions均会被添加模块名前缀,并且会拼接上父级的前缀
- 插件(实际就是一个高阶函数)的实现采用的是发布者订阅模式,当mutations触发时,发布通知,让所有的订阅依次执行