Appearance
Vuex
Vuex 是Vue专属的状态管理库
安装
同样是Vue.use安装
js
// store.js
function install (_Vue) {
if (Vue && _Vue === Vue) {
// 重复安装
return
}
Vue = _Vue
applyMixin(Vue)
}
// mixin.js
function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// Vue2 使用 mixin 注入到beforeCreate
Vue.mixin({ beforeCreate: vuexInit })
}
}
function vuexInit () {
const options = this.$options
if (options.store) {
// 根节点
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 非根节点向上递归,类似 router
this.$store = options.parent.$store
}
}
初始化
一般是使用 new Vuex.Store(options) 传入配置对象,返回store实例
js
// store.js
class Store {
constructor(options = {}) {
const {
plugins = [],
strict = false
} = options
// 一些参数初始化
this._committing = false // 提交操作中的标识
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// 将 modules 的 state 写成树状 ModuleCollection
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// 绑定 $store.commit/dispatch 的 上下文
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)
}
// 根部的 state,不包含module的state
const state = this._modules.root.state
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)
//...
}
}
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length // 根 state 标识
// 当前的state的命名空间
const namespace = store._modules.getNamespace(path)
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
// 重复的命名空间
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
// 每个命名空间是存到 store 的 _modulesNamespaceMap 上
store._modulesNamespaceMap[namespace] = module
}
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
// .. 响应式注册 modules 的state 到父级state上
// 因此可以通过 $store.state[modulesName] 获取到嵌套的state
Vue.set(parentState, moduleName, module.state)
})
}
// 生成local,用于mutation或者action或者getter调用
const local = module.context = makeLocalContext(store, namespace, path)
// 绑定local,以便外部调用的时候可以找到对应的state
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key // module/mutation
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
返回用于store执行getters等的上下文
js
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === '' // 是否有设置命名空间
// local 就是返回的结果
// 如果没有命名空间,就用回 store实例上 的方法
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
if (__DEV__ && !store._actions[type]) {
return
}
}
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
if (__DEV__ && !store._mutations[type]) {
return
}
}
store.commit(type, payload, options)
}
}
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters // 跟state或者是没有命名空间的
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function makeLocalGetters (store, namespace) {
if (!store._makeLocalGettersCache[namespace]) {
const gettersProxy = {}
const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
// 遍历所有存储到getters上的
// 不是这个命名空间的跳过
if (type.slice(0, splitPos) !== namespace) return
// 将属于这个命名空间的存起来缓存
const localType = type.slice(splitPos)
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
store._makeLocalGettersCache[namespace] = gettersProxy
}
// 有缓存过直接拿
return store._makeLocalGettersCache[namespace]
}
local各模块相关的注册
js
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)
})
// 最后会注册到 store._mutations 以数组形式存储
}
function registerAction (store, type, handler, local) {
// 注册action,同样是以数组形式存储
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)) {
// 最终转换,返回一个promise
res = Promise.resolve(res)
}
return res
})
}
function registerGetter (store, type, rawGetter, local) {
// 以函数形式,存储getters
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
resetStoreVM
js
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
store.getters = {}
store._makeLocalGettersCache = Object.create(null)
// 初始化是空对象,在local安装之后会对应每个命名空间的getter
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// key是包含命名空间的
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// 上面将命名空间作为key,对应的wrappedGetter作为value,保存到computed对象
// 同时设置了 store.getters 属性,之后可以直接 $store.getters[key] 取值
const silent = Vue.config.silent
Vue.config.silent = true
// 本质上 state和getter 还是通过 Vue 建立的 vm实例,所以vuex才是Vue专属的
store._vm = new Vue({
data: {
$$state: state
// store 类中,针对 store的state,是通过设置了get来重定位到 $$state 的值
},
computed
// getter 转化成computed,妙啊
})
if (oldVm) {
// 如果执行动态注入模块,需要销毁旧组件实例
if (hot) {
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
API
Vuex 进行数据修改,本质上就是对store._vm里面的data进行修改
mutation
mutation 的注册参考上面installModule
js
// 当使用 $store.commit 来调用
// 这是没有命名空间的情况
commit (_type, _payload, _options) {
// 参数格式化
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
// 注册过程中,将mutation函数放在数组中
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
// 逐一调用注册的处理函数
handler(payload)
// 最后handler的传入参数是 local.state 和 payload
})
})
// ...
}
action
mutation适用于同步修改数据的操作,如果需要跟后端进行交互的异步操作,则是需要使用action
js
// $store.dispatch 调用
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
// 初始化过程中会注册actions
if (!entry) {
return
}
try {
this._actionSubscribers
.slice()
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
}
// 执行注册的actions
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
// entry 返回的都是 已经resolve的Promise
// dispatch 最终返回的是 Promise
return new Promise((resolve, reject) => {
result.then(res => {
try {
// 如果有store的订阅就会执行
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
}
reject(error)
})
})
}
语法糖
mapState为例子,其他几个辅助函数都一样流程原理
用于将 store 的 state 作为 vm实例的compputed 使用
js
// helper.js
mapState = normalizeNamespace((namespace, states) => {
const res = {}
// normalizeMap 将传入的states格式化成 { key, val } 形式
// 其中val就是传入的定义,可能是函数,或者是字符串
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
// res 是返回给computed,执行的this是vm上下文
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
// 如果有命名空间
// 在初始化的时候会注册,这里是到 store._modulesNamespaceMap 取出module
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// module.context 其实就是之前生成的 local
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 normalizeNamespace (fn) {
// 返回一个函数,函数返回的结果是一个对象作为computed
return (namespace, map) => {
// 如果传入第一个参数是字符串,就认为是 命名空间
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
// 返回上面的res作为compputed
return fn(namespace, map)
}
}
动态更新模块
适用于初始化store之后,在重新注入新的模块
registerModule
js
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)
// 重新设立vm实例,并销毁旧的vm实例
resetStoreVM(this, this.state)
}
unregisterModule
js
unregisterModule (path) {
if (typeof path === 'string') path = [path]
// 在模块中删除,之后重新初始化注册的时候就不会注册这个子模块
this._modules.unregister(path)
this._withCommit(() => {
// 删除指定的模块的state的响应式属性
const parentState = getNestedState(this.state, path.slice(0, -1))
Vue.delete(parentState, path[path.length - 1])
})
resetStore(this)
}
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
// 重新执行初始化
installModule(store, state, [], store._modules.root, true)
// reset vm
resetStoreVM(store, state, hot)
}