一、问题:
1、vuex是如何注入的
2、vuex是实现响应的
3、vuex中state, getters,mutations, actions的注册和触发
二、vuex的注入
vue中使用vuex, 实际上就是调用vuex中install方法
// 判断之前vue中是否注册过vuex,如果注册过,提示警告,如果没有调用applyMixin
function install (_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
// applyMinxin中, 其实就是通过vue的mixin方法, 在beftoreCreate中注入$store
// new Vue({store}), 挂载在vue中组件就可以通过this.$option.store访问到store
// 子组件则可以通过访问父组件的$store得到, this.$options.parent.$store
function applyMixin(Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
三、vuex中store
vue.use(vuex)只是给vuex注入给vuex, vuex的实现在store中, export default new vuex.Store({...})
vuex中store的基本构成
export class Store {
constructor (options = {}) {
// 如果之前没有使用Vue.use(Vux)并且window中挂载有Vue, 进行vuex的注册
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
this._committing = false // 提交状态
this._actions = Object.create(null) // actions的注册列表
this._actionSubscribers = [] // actionSubScribers的注册列表
this._mutations = Object.create(null) // mutations的注册列表
this._wrappedGetters = Object.create(null) // getter的注册列表
this._modules = new ModuleCollection(options) // modules的注册列表
this._modulesNamespaceMap = Object.create(null) //含有namespaced的module注册列表
this._subscribers = [] // subscriber的注册列表
this._watcherVM = new Vue() //为watch提供监听方法this._watcherVM.$watch
this._makeLocalGettersCache = Object.create(null) // namespaced的module中getters的代理缓存
//绑定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)
}
// 注册根module, 循环注册子module, 收集模块的getter, mutation, action
installModule(this, state, [], this._modules.root)
// 初始化store、getters的数据,实现响应式,
resetStoreVM(this, state)
}
// 当访问store.state时候, 实际访问的为store._vm._data.$$state
get state () {
return this._vm._data.$$state
}
// 当设置store.state时候, 提示报错, 不允许设置
set state (v) {
if (__DEV__) {
assert(false, `use store.replaceState() to explicit replace store state.`)
}
}
// commit函数的实现:执行对应type的mutations函数,并执行subscribers函数
commit (_type, _payload, _options) {}
// dispatch函数的实现: 执行对应type的actions注册函数,并执行subscribeAction函数
dispatch (_type, _payload) {}
//注册 subscribers函数
subscribe (fn, options) {}
//注册 subscribeAction函数
subscribeAction (fn, options) {}
//注册 watch方法,响应式监听fn的返回, 当值改变时调用回调函数
watch (getter, cb, options) {} }
//替换store根状态
replaceState (state) {}
//注册module模块
registerModule (path, rawModule, options = {}) {}
// 移除module模块
unregisterModule (path) {}
// module中是否含有该模块
hasModule (path) {}
// 热替换新的action和mutation
hotUpdate (newOptions) {}
// 合法更改store中的数据
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
}
installModule的实现
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
//getNamespace ,如果模块的namespaced为true, 则返回类似key/key格式,为false则返回 ""
const namespace = store._modules.getNamespace(path)
// 判断是否存在相同namespace的模块
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module
}
// 子模块热更新注册state数据,
if (!isRoot && !hot) {
// 找到子模块的父state,
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取子模块的模块名
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 父模块的state注册上子模块的state,
// 例store.state.module1.count
// 父模块state.子模块名.子模块state中属性
Vue.set(parentState, moduleName, module.state)
})
}
// 获取当前模块的getters, state, mutation, action,
const local = module.context = makeLocalContext(store, namespace, path)
// 遍历模块的mutations中的方法, 按照type类型放入store._mutations中
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 遍历模块的action中的方法, 按照type类型放入store._actions中
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 遍历模块中的getter的属性,放入store._wrappedGetters中
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 遍历模块中modules, 执行installModule,
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
makeLocalContext的实现
// 如果没有注册namespaced,则直接使用store.dispatch, store.commit, store.getter即可
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
// 这里主要兼容处理参数, commit有两种传参方式,
// 1、commit("ADD_COUNT", 1);
// 2、commit({type: "ADD_COUNT", num: 1});
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 非根节点在type添加前缀, 前缀namespace依赖模块的namespanced是否为true
if (!options || !options.root) {
type = namespace + type
}
// 底层还是调用store.dispatch
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和state需要进行拦截处理
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
// 问题: 为什么要进行拦截, 当namespace为true, 页面中使用getters还会把模块带上,store.getters["module/name"],无需代理
// get答: 这里local的是为在子模块如调用自身getters无需加上模块名
// mutations: { ADD_NUM({getters}) { getters.key } }
// state在store的储存方式为store.state.子模块名 = 子模块.state
// getter在store的储存方式为store.getters[namespaced+key] = getter
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
// 注册getters的时候,注册当前key的值如下,
// const namespacedType = namespace + key
// 移除namespacd,剩下的key,就是注册到当前模块getter的key值
const localType = type.slice(splitPos)
// 设置代理,当子模块中访问当子模块的属性, 实际访问的是store.getters[type]
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
store._makeLocalGettersCache[namespace] = gettersProxy
}
return store._makeLocalGettersCache[namespace]
}
// 根据path返回对应的层级的store
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
registerMutation、registerAction、registerGetter的实现
// 为什么格式为_mutations: {[type]: []}, 同个type类型可以有多个方法
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)
})
}
// _actions的格式同理
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload) {
// action中的参数有{dispatch、commit、getters、state、rootState、roomState}
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
// action为异步方法,
if (!isPromise(res)) {
res = Promise.resolve(res)
}
})
}
// getter一个类型只允许一个方法
function registerGetter (store, type, rawGetter, local) {
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的实现
function resetStoreVM (store, state, hot) {
store.getters = {}
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
//使用computed来利用它的延迟缓存机制
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true
})
})
// 响应式的实现依赖于vue
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
四、vuex中的module
module模块
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
this._children = Object.create(null) // 储存模块下面的子模块
this._rawModule = rawModule // 储存原始对象
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// 判断是否设置namespaced
get namespaced () {
return !!this._rawModule.namespaced
}
// 添加子模块
addChild (key, module) {
this._children[key] = module
}
//删除子模块
removeChild (key) {
delete this._children[key]
}
// 获取所有子模块
getChild (key) {
return this._children[key]
}
// 是否存在该子模块
hasChild (key) {
return key in this._children
}
// 更新模块
update (rawModule) {
this._rawModule.namespaced = rawModule.namespaced
if (rawModule.actions) {
this._rawModule.actions = rawModule.actions
}
if (rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations
}
if (rawModule.getters) {
this._rawModule.getters = rawModule.getters
}
}
// 遍历模块的子模块
forEachChild (fn) {
forEachValue(this._children, fn)
}
// 遍历模块的getters方法
forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
// 遍历模块的action方法
forEachAction (fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
// 遍历模块的mutation方法
forEachMutation (fn) {
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
}
module-collection, 在module的基本进行进一步的封装
export default class ModuleCollection {
// store中_modules就是通过new ModuleCollection得来
// this._modules = new ModuleCollection(options)
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
// 通过path获取对应的模块
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
// 获取namespace, 取决于模块是否设置namespaced为true, 如果为true, 则模块key/模块key, 为false, 则返回“”
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
// 更新模块
update (rawRootModule) {
update([], this.root, rawRootModule)
}
// 注册模块
register (path, rawModule, runtime = true) {
// 通过options封装module模块
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule // this.root为初始化的store
} else {
const parent = this.get(path.slice(0, -1)) // 获取当前节点父节点
parent.addChild(path[path.length - 1], newModule) // 给父节点添加当前模块
}
// new moduleCollect(options)时候
// 如果options中有modules, 那么遍历modules,收集下面子module为父module的children
// parent.addChild(path[path.length - 1], newModule)
// 下面只需要调用module的forEachChild
// 结构如下
// - root(Module)
// -- root._children[子module1(Module), 子module2(Module)]
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
// 移除module
unregister (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
const child = parent.getChild(key)
parent.removeChild(key)
}
// 判断是否存在该模块
isRegistered (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
if (parent) {
return parent.hasChild(key)
}
return false
}
}
// 更新模块的实际实现
function update (path, targetModule, newModule) { targetModule.update(newModule) //调用的是Module类的更新, 更新对应模块的actions、mutations、getters和namespaced
// 更新模块的modules
if (newModule.modules) {
for (const key in newModule.modules) {
if (!targetModule.getChild(key)) {
return
}
update(
path.concat(key),
targetModule.getChild(key),
newModule.modules[key]
)
}
}
}
1、module构建了最基本的结构
- _childrens
- addChild
- removeChild
- getChild
- hasChild
- update(actions、mutations、getters、namespaced)
2、moduleCollect在module的基础添加了对store的处理, 主要通过path实现store的层级结构
- root
- get
- getNamespace
- update(actions、mutations、getters、namespaced、modules)
- register (根节点注册为root, 非根节点则通过addChild注册到父节点上)
- unregister
- isRegistered
五、解答问题
1、vuex是如何注入的
- vue.use(vuex)调用vuex的install方法
- install执行了方法applyMixin
- 在applyMixin中, 生命周期beforeCreate通过获取option.parent.store插入
2、vuex是实现响应的
-
vuex中的data, getter都是存在代理的
-
本质上通过new Vue实现的响应
new Vue({ data: { $$state: state }, computed }) -
后面访问state的时候
get state () { return this._vm._data.$$state }
-
访问getters的时候
Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters })
3、vuex中state, getters,mutations, actions的注册和触发
如上文, 主要通过key构成path
参考源码
参考文章