本文将以代码 + 注释的方式讲解vuex的实现
在开始之前,我们先看一个vuex的使用举例
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
actions: {
increment({ commit }) {
commit('increment')
}
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
countGetter: state => state.count
}
})
new Vue({
el: '#app',
store,
render: h => h(App)
})
我们把基本的功能都使用到了,那么接下来我们来看一下vuex中都做了什么
vuex的实现过程
- 使用vue.mixin混入beforeCreated生命周期,注入$store字段赋值vuex.Store实例
- 初始化用户相关配置(state、actions、mutations、getters、modules)
- 根节点相关的配置(state、actions、mutations、getters)命名为root
- modules下相关配置(state、actions、mutations、getters)以树形结构存在root module的 _children 内
- 所有的module挂载到 store 实例的 _modulesNamespaceMap 字段
- actions、mutations、getters以函数名 + 命名空间为key,具体函数为value分别挂载到 store 实例的 _actions、_mutations、_wrappedGetters 字段可以直接访问
- 初始化用户响应式vm实例
- 通过vue.computed计算属性,实现用户getter方法(每次调用都会动态计算state)
vuex注册
在上面的举例中,首先我们注册了vuex插件
Vue.use(Vuex)
Vue.use 会调用 Vuex 的 install 方法(vue开发插件),我们看一下 vuex 的 install 的方法实现:
// 文件路径:/src/store.js => install 函数
let Vue // bind on install Vue实例
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
我们注意到 vuex 中使用到一个 Vue 全局字段,在 install 的时候会先判断是否重复注册,防止重复注册。
接下来调用了 applyMixin 方法:
// 文件路径:/src/mixin.js
function applyMixin(Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// vue2.x 执行
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 // 获取 vue 实例的配置项
// 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
}
}
}
在 applyMixin 方法中,首先获取 vue 的版本号,通过这一步,我们可以发现 vuex 分别为 vue1 和 vue2 做了对应的支持。因为现在都在使用 vue2,我们只看 vue2 支持部分,这里使用 Vue.mixin 在 vue 实例的 beforeCreate 生命周期之前执行 vuexInit 方法。
vuexInit 方法的实现:
- 获取 vue 实例的配置项 vue.$options
- 如果配置项中含有 store 字段(开头举例时声明的 store 变量)
- 当前 vue 实例(this)赋值 $store 字段为 配置项的 store 字段,如果 store 字段为函数则执行赋值返回结果,其他情况直接赋值
- 如果配置项不包含 store 字段,且当前实例有父级实例、父级实例已声明 $store 字段
- 当前 vue 实例(this)赋值 $store 字段为 父级实例的 $store 字段
走到这一步,我们完成了vuex插件的注册操作。
vuex实例化
我们在注册 vuex 插件的注册之后,实例化了 Vuex.Store。
const store = new Vuex.Store({
state: {
count: 0
},
actions: {
increment({ commit }) {
commit('increment')
}
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
countGetter: state => state.count
}
})
我们看一下 new Vuex.Store 实例化都做什么事情。
// 文件目录:/src/store.js => class Store
// 只保留了关键代码
class Store {
// 构造函数
constructor (options = {}) {
// 当全局 Vue 字段没有被赋值,通过 window.Vue 注册 vuex 插件
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
// 代码省略 --- 这里声明了很多字段,不做多说
this._modules = new ModuleCollection(options) // 初始化 modules 实例
// 对用户暴露的dispatch方法
// 也就是我们常用的this.$store.dispatch
this.dispatch = function boundDispatch (type, payload) {
// 使用call方法绑定this
// 防止this.$store.dispatch出现dispatch函数内部this指向错误
return dispatch.call(store, type, payload)
}
// 对用户暴露的commit方法
// 也就是我们常用的this.$store.commit
this.commit = function boundCommit (type, payload, options) {
// 使用call方法绑定this
// 防止this.$store.commit出现commit函数内部this指向错误
return commit.call(store, type, payload, options)
}
// 初始化根 module
installModule(this, state, [], this._modules.root)
// 初始化 vue 实例负责 vuex 响应式
resetStoreVM(this, state)
// 注册插件
plugins.forEach(plugin => plugin(this))
// vue开发者工具初始化 - 省略
}
}
主要步骤:
- 如果之前未注册 vuex 插件,则注册 vuex 插件
- 初始化module
- 初始化dispatch函数,也就是我们常用的this.$store.dispatch
- 初始化commit函数,也就是我们常用的this.$store.commit
- 初始化根节点的module
- 初始化vue响应式
- 注册插件
- 初始化vue开发者工具
Module
我们声明的 state、mutations、actions、getters 的组合为一个 Module(常见为命名空间)
// 文件目录:/src/module/module.js
// 具体代码已省略
class Module {
constructor (rawModule, runtime) {} // 构造函数
get namespaced () {} // 当前 module 是否已命名空间
addChild (key, module) {} // 添加子节点 module
removeChild (key) {} // 移除子节点
getChild (key) {} // 返回指定子节点
update (rawModule) {} // 更新 module(更新namespaced、actions、mutations、actions)
forEachChild (fn) {} // 遍历全部子节点执行回调
forEachGetter (fn) {} // 遍历全部getters执行回调
forEachAction (fn) {} // 遍历全部actions执行回调
forEachMutation (fn) {} // 遍历全部mutations执行回调
}
注册actions、mutations、getters
/**
* 注册mutation函数
* params: {
* store: store实例
* type: mutation函数路径(命名空间地址 + 函数名)
* handler: mutation函数
* local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
* }
*/
function registerMutation (store, type, handler, local) {
// mutation使用数组保存(可以声明多个相同路径的mutation)
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
// 使用call方法,保证mutation函数内部this指向了store实例
// local.state为当前module命名空间的state
// payload为用户commit时的实参
handler.call(store, local.state, payload)
})
}
/**
* 注册action函数
* params: {
* store: store实例
* type: action函数路径(命名空间地址 + 函数名)
* handler: action函数
* local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
* }
*/
function registerAction (store, type, handler, local) {
// action使用数组保存(可以声明多个相同路径的action)
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload, cb) {
// 使用call方法,保证action函数内部this指向了store实例
let res = handler.call(store, {
dispatch: local.dispatch, // 当前module命名空间的dispatch
commit: local.commit, // 当前module命名空间的commit
getters: local.getters, // 当前module命名空间的getters
state: local.state, // 当前module命名空间的state
rootGetters: store.getters, // 根节点的getters
rootState: store.state // 根节点的state
}, payload, cb)
if (!isPromise(res)) {
// 始终保证action返回的结果为一个promise对象
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
/**
* 注册getter函数
* params: {
* store: store实例
* type: getter函数路径(命名空间地址 + 函数名)
* rawGetter: getter函数
* local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
* }
*/
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
// 注意:getter与actions、mutations不同,同一个type是唯一的
store._wrappedGetters[type] = function wrappedGetter (store) {
// 执行getter函数
return rawGetter(
local.state, // 当前module命名空间的state
local.getters, // 当前module命名空间的getters
store.state, // 根节点的state
store.getters // 根节点的getters
)
}
}
dispatch函数
// 文件目录:/src/store => class Store => dispatch
/**
* _type: 调用action路径
* _payload: 调用action参数
*/
dispatch (_type, _payload) {
// check object-style dispatch
const {
type, // 调用action路径
payload // 调用action参数
} = unifyObjectStyle(_type, _payload) // 兼容处理_type为Object的情况
const action = { type, payload }
const entry = this._actions[type] // 取出对应的action函数
if (!entry) {
// 不存在action终止执行
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
// before dispatch 生命周期遍历回调
this._actionSubscribers
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
// 遍历执行actio函数
// vuex在注册action时,内部将函数返回的结果使用promise包裹
// 保证返回的肯定是一个promise对象
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload))) // 多个函数使用Prmoise.all
: entry[0](payload)
return result.then(res => {
try {
// after dispatch 生命周期遍历回调
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
// 返回结果,这里注意如果配置了多个相同的action,返回的是一个结果集合
return res
})
}
Commit函数
// 文件目录:/src/store => class Store => commit
/**
* _type: 调用commit路径
* _payload: 调用commit参数
* _options: commit配置参数
*/
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options) // 兼容处理_type为Object的情况
const mutation = { type, payload }
const entry = this._mutations[type] // 取出对应的mutation函数
if (!entry) {
// 不存在_mutations终止执行
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
// 遍历执行mutation函数
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 遍历调用 subscribe 生命周期回调
this._subscribers.forEach(sub => sub(mutation, this.state))
}
工具函数
helper函数
// 文件目录:/src/helpers.js
// 帮助函数
// 将数组、对象转为map格式
// 例:normalizeMap(['test']) => { key: 'test', val: 'test' }
// 例:normalizeMap({ test: 1 }) => { key: 'test', val: 1 }
function normalizeMap (map) {
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
// 格式化namespace
function normalizeNamespace (fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
// 当传入第一个参数不为string时,默认认为是从根节点访问
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
// namespace最后一位不是 '/' 为namespace添加 '/' 结尾
namespace += '/' // 从 namespace 开始访问
}
return fn(namespace, map)
}
}
// 通过namespace获取module
// store:store实例
// helper:开发模式时打日志使用
// namespace:namespace访问路径
function getModuleByNamespace (store, helper, namespace) {
// 通过_modulesNamespaceMap访问
// _modulesNamespaceMap在installModule函数中注册
const module = store._modulesNamespaceMap[namespace]
if (process.env.NODE_ENV !== 'production' && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
mapState函数 & mapGetters函数
使用示例
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
}),
...mapGetters({
a: 'a',
b: 'b'
})
}
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
}),
...mapGetters({
a: 'a',
b: 'b'
})
}
实现代码:mapState
// 文件目录:/src/helpers.js
const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 格式化states遍历
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
// 这里this指向vue实例
let state = this.$store.state // store根节点state
let getters = this.$store.getters // store根节点getters
if (namespace) {
// 如果用户指定了namespace则取对应的module
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// module.context在installModul初始化(上文的local)
state = module.context.state // 取module的state
getters = module.context.getters // 取module的getter
}
return typeof val === 'function'
? val.call(this, state, getters) // 如果传入的是函数,则返回执行结果
: state[val] // 直接返回对应字段
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res // 返回结果集合
})
实现代码:mapGetters
// 文件目录:/src/helpers.js
const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
// 格式化states遍历
normalizeMap(getters).forEach(({ key, val }) => {
val = namespace + val // 拼接namespace
res[key] = function mappedGetter () {
// 不存在的module终止
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
// 返回getter
return this.$store.getters[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res // 返回结果集合
})
mapMutations函数 & mapActions函数
使用示例
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
]),
...mapMutations([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
]),
...mapMutations([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
实现代码:mapActions
// 文件目录:/src/helpers.js
const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
// 格式化actions遍历
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
// get dispatch function from store
let dispatch = this.$store.dispatch // 先取根节点的dispatch
if (namespace) {
// 不存在的module终止
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
dispatch = module.context.dispatch // 取module的dispatch
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
实现代码:mapMutations
// 文件目录:/src/helpers.js
const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
// 格式化actions遍历
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation (...args) {
// Get the commit method from store
let commit = this.$store.commit // 先取根节点的dispatch
if (namespace) {
// 不存在的module终止
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit // 取module的dispatch
}
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})