Vuex源码阅读笔记

最近阅读了Vuex3.x的源码,这篇文章主要是对Vuex的实现思路做一个分析并且最后实现一个mini-vuex。

源码目录结构

Vuex源码目录

  • module

    • module-collection :模块收集相关逻辑,包括模块对象树的创建、模块注册、模块更新等
    • module :模块对象
  • plugins

    • devtool :vue-devtool插件中的逻辑,主要处理时光旅行等
    • logger:日志打印
  • helpers:封装了对命名空间的相关处理

  • index.cjs/index/index.mjs :vuex的主入口,导出了vuex的常用api

  • mixin:处理store在Vue实例中的注入

  • store:vuex核心逻辑的实现,也是我们重点阅读的地方

  • util:工具方法封装

Vuex 原理

Vuex以是插件的形式被安装进Vue中的。

大概原理如下:

Vue.use(Vuex)时,会执行Vuex中的install方法。因此在install方法中,对每个Vue实例的beforeCreate生命周期里混入同一个store实例($store)。这样我们在不同组件中调用Vuex的方法实际上是在调用同一个$store上的方法,有种类似全局变量的感觉。

关于Vuexstate响应式的实现,实际上就是创建了一个Vue实例并且把state赋值给data属性。

getter则是典型的Vue2响应式实现原理 —— 使用Object.definePropertiesgetter中的属性进行劫持从而在数据改变的情况下进行重新计算。

mutationsactions,大同小异,在创建Vuex对象的时候保存了传递过来的mutationsactions,分别在commitdispatch中拿到对应的方法,封装返回出另一个方法并传递相应参数,这也就是我们在commitdispatch自定义方法里能拿到state等参数的原因。

Vuex安装

install

我们首先从插件的安装开始(store.js 539 - 550 行)

1
2
3
4
5
6
7
8
9
10
11
12
export 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)
}

前面是一些防止多次安装的校验,之后把传入的Vue实例保存起来,并调用applyMixin方法。顾名思义,applyMixin的作用就是全局混入唯一$store实例。

applyMixin

applyMixinmixin.js文件导出的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default function (Vue) {
// 拿到Vue版本号,2.x和1.分开处理,这里我们主要看2.x的
const version = Number(Vue.version.split('.')[0])

if (version >= 2) {
// 直接在beforeCreate中混入初始化方法
Vue.mixin({ beforeCreate: vuexInit })
} else {
// 1.0x逻辑 ...
}

function vuexInit () {
// 这里的$options 是new Vue的时候传入的。在main.js里可以看到。
const options = this.$options
// store 注入到每一个Vue的实例中
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
}
}
}

之后,每一个Vue实例对象中都会有同一个$store属性。

Store对象详解

store.js是整个Vuex逻辑的实现部分,我会按构造方法、模块收集、statemutationsactionsgetters的注册、store的初始化、api的实现、其它辅助函数的顺序进行分析。源文件的注释写的比较详细,我会直接把注释翻译出来,如果有条件的话可以通过直接打断点调试的方式理解vuex初始化流程。

构造方法

1
2
3
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}

通过script标签的方式引入Vuex,直接安装,不需要调用Vue.use。

1
2
3
4
5
6
7
8
9

if (__DEV__) {
// 必须使用 Vue.use(Vuex) 创建store实例
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
// 当前浏览器不支持 promise,需要使用 polyfill
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
// Store 需要用 new 来实例化
assert(this instanceof Store, `store must be called with the new operator.`)
}

开发环境的条件断言判断。这里的assertutil.js里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const {
plugins = [], // 插件
strict = false // 严格模式,
} = options

// 数据初始化
this._committing = false // 正在进行commit的标识
this._actions = Object.create(null) // 用户定义的actions
this._actionSubscribers = [] // actions订阅
this._mutations = Object.create(null) // 用户定义的mutations
this._wrappedGetters = Object.create(null) // 用户定义的getters
this._modules = new ModuleCollection(options) // 模块收集器,用于构造模块树
this._modulesNamespaceMap = Object.create(null) // 存储模块命名空间的关系
this._subscribers = [] // 订阅
this._watcherVM = new Vue() // 用于使用 $watch 观测 getters
this._makeLocalGettersCache = Object.create(null) // 用来存放生成的本地 getters 的缓存

// 绑定 dispatch 和 commit 的 this 到 store 对象,确保在这两个方法中的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)
}

// 严格模式
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
// 并将模块中的 getters 收集到 _wrappedGetters 里
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化 store vm 实例以提供响应式,同时将 _wrappedGetters 作为 computed 属性注册
resetStoreVM(this, state)

// 执行插件
plugins.forEach(plugin => plugin(this))
// devtool 初始化
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}

首先声明Store对象一些内部变量。存放用户自定义的actionsmutationsgetters等变量。

这里使用了 Object.create(null) 的方式创建对象, 这个方式创建的对象是没有原型链的,即 Object.create(null).__proto__undefined

接着调用了installModuleresetStoreVM方法用于注册模块,构建模块树和收集getters依赖。

因为Vuex 本身是单一状态树,应用的所有状态都包含在一个大对象内,随着应用规模的不断增长,这个 store变得非常臃肿。为了解决这个问题,Vuex 允许我们把 storemodule(模块)。每一个模块包含各自的 statemutationsactionsgetters,甚至是嵌套模块。

我们首先看一下模块收集部分的代码,之后再来看installModule的处理。

模块收集机制

模块收集的机制在module-collection.js文件中。

1
2
3
4
5
constructor (rawRootModule) {
// register root module (Vuex.Store options)
// 注册根模块,这里的rawRootModule就是new Vue.Store的参数options
this.register([], rawRootModule, false)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
register (path, rawModule, runtime = true) {
// 开发环境 判断原始模块是否符合要求
if (__DEV__) {
assertRawModule(path, rawModule)
}
// 构建模块树
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}

// register nested modules
// 递归注册子模块
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}

模块对象在 module.js 文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item 存储子模块
this._children = Object.create(null)
// Store the origin module object which passed by programmer 存储用户定义的原始模块
this._rawModule = rawModule
const rawState = rawModule.state

// Store the origin module's state 存储模块的state,可能是方法或对象
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// 是否有相同的namespace
get namespaced () {
return !!this._rawModule.namespaced
}
// 对子模块的一些操作
addChild (key, module) {}
removeChild (key) {}
getChild (key) {}
hasChild (key) {}
// 更新模块,就是重新赋值各个属性
update (rawModule) {}
// 工具遍历方法
forEachChild (fn) {}
forEachGetter (fn) {}
forEachAction (fn) {}
forEachMutation (fn) {}
}

我们已经看完了模块收集部分的代码,也就是说他会遍历所有Vuex中定义的所有模块,初始化成模块对象,并将它们构建成一个模块树。这样的好处是我们在执行commitdispatch等操作的时候,就能通过命名空间找到对应模块下的mutationsactions

接下来我们回到installModule方法中,来看vuex中各个属性的注册逻辑。

模块安装和属性注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function installModule (store, rootState, path, module, hot) {
// 是否是根模块
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)

// register in namespace map
// 模块命名空间在map中已经有了
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、mutation、action、getter进行注册
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
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('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}

const local = module.context = makeLocalContext(store, namespace, path)

module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
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)
})
}

installModule 函数可接收5个参数,storerootStatepathmodulehotstore 表示当前 Store 实例,rootState 表示根 statepath 表示当前嵌套模块的路径数组,module 表示当前安装的模块,hot 当动态改变 modules 或者热更新的时候为 true

首先通过 path 数组的长度判断是否为根。我们在构造方法调用的时候是installModule(this, state, [], options),所以这里 isRoottruemodule 为传入的 options,我们拿到了 module下的 stateactionsmutationsgetters以及嵌套的 modules,并将其中所有的getters存储到_wrappedGetters中。

注册state

在不是根组件且不是热重载条件的情况下,通过getNestedState方法获得该模块父级的state和其所在的 moduleName ,调用 Vue.set(parentState, moduleName, module.state) 方法将其state设置到父级state对象的moduleName属性中,由此实现该模块的state注册(首次执行这里,因为是根目录注册,所以并不会执行该条件中的方法)。getNestedState方法代码很简单,reduce path获得state

值得一提的是,这里整个注册state的方法作为参数传给了_withCommit方法,在这之后执行注册操作。这是为了避免用户直接操作state,_withCommit方法实际上就是将_committing标识位置成true,之后执行回调,最后将标识位复原。如果用户不通过mutation而直接操作state,此时的_committing没有改变, 就会报错。

这里对state操作的监控是在严格模式下进行的,在resetStoreVM方法中进行赋值。

之后拿到local变量,也就是modulecontext,这个变量用来传给mapStatemapGetters等辅助方法的目的在于生成当前module的dispatchcommitgettersstate,抹平差异化。在这个方法里同时对gettersstate进行了处理(425 - 434 行)

1
2
3
4
5
6
7
8
9
10
11
12
13
// getters and state object must be gotten lazily
// because they will be changed by vm update
// 由于getters 和 state 是响应式的,在虚拟dom更新的时候进行改变,因此需要懒加载(延迟获得??不太会翻译)
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})

注册mutation

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @param {Object} store 对象
* @param {String} type 类型
* @param {Function} handler 用户自定义的mutation方法
* @param {Object} local
*/
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)
})
}

通过_mutations[type]获取到对应的mutation,push一个新的处理方法,这个方法将设置在mutations type上对应的 handler 进行了封装,给原函数传入了state。从而在执行 commit 的时候,能获取到state以及payload

注册action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function registerAction (store, type, handler, local) {
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)) {
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}

action的注册和mutation大同小异,主要区别在于action用promise进行了封装,这也就是我们需要在action中使用异步的原因。

注册getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (__DEV__) {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}

对getter的注册,直接给原getter传入需要的参数,这样就能在自定义getters方法的形参中获取到。

初始化store组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function resetStoreVM (store, state, hot) {
const oldVm = store._vm

// bind store public getters
store.getters = {}
// reset local getters cache 重置getters缓存
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})

// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
// 使用一个 Vue 实例对象存储 state 树,警告以防止用户添加的一些全局mixins
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent

// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}

if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}

resetStoreVM方法做了两件事:

  • 循环处理过的getters,并新建computed对象进行存储,通过Object.defineProperty方法为getters对象建立属性,使用户可以通过this.$store访问到getters

  • 设置新的store vm对象,将当前初始化的state以及getters作为computed属性,并且封装state$$state禁止直接修改state —— 在第73 - 81行可以看到,当使用$store.state时返回$$state

    1
    2
    3
    4
    5
    6
    7
    8
    9
    get state () {
    return this._vm._data.$$state
    }

    set state (v) {
    if (__DEV__) {
    assert(false, `use store.replaceState() to explicit replace store state.`)
    }
    }

api的实现

commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
commit (_type, _payload, _options) {
// check object-style commit
// 统一成对象风格
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)

const mutation = { type, payload }
const entry = this._mutations[type]

// 开发环境警告代码...

this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 订阅 mutation 执行
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))

}

commit的参数有直接传参和通过对象的方式传参。这里使用unifyObjectStyle工具方法将参数统一转换为对象形式。

commit 支持 3 个参数,type 表示 mutation 的类型,payload 表示额外的参数,options 表示一些配置,比如 silent 等,稍后会用到。commit 函数首先对 type 的类型做了判断,处理了 typeobject 的情况,接着根据 type 去查找对应的 mutation,如果找不到,则输出一条错误信息,否则遍历这个 type 对应的 mutation 对象数组,执行 handler(payload) 方法,这个方法就是之前定义的 wrappedMutationHandler(handler),执行它就相当于执行了 registerMutation 注册的回调函数,并把当前模块的 state 和 额外参数 payload 作为参数传入。注意这里我们依然使用了 this._withCommit 的方法提交 mutationcommit 函数的最后,判断如果不是静默模式,则遍历 this._subscribers,调用回调函数,并把 mutation 和当前的根 state 作为参数传入。

通过this._mutations[type]获取commit调用的mutation,之后遍历执行。

dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
dispatch (_type, _payload) {
// check object-style dispatch
// 统一成对象风格
const {
type,
payload
} = unifyObjectStyle(_type, _payload)

const action = { type, payload }
const entry = this._actions[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}

try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}

const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)

return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}

dispatch 的代码稍微长一点,主要区别在于拿到具体方法后使用promise执行,其中涉及到一些错误的判断和处理。

辅助函数

复赋值函数在 helper.js 文件中

mapSate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export const mapState = normalizeNamespace((namespace, states) => {
const res = {} // 用于接收map之后的state属性
if (__DEV__ && !isValidMap(states)) {
console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
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]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})

mapState 可以接受一个对象,也可以接收一个数组,因此首先使用 normalizeMap 将states参数转换为固定的对象形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Normalize the map
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}

之后遍历转换后states对象,每个新对象每个元素都返回一个新的函数 mappedState,函数对val的类型判断,如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的stategetters 作为参数,返回值作为 mappedState 的返回值;否则直接把this.$store.state[val]作为 mappedState 的返回值。

mapState 的作用是把全局的 stategetters 映射到当前组件的 computed 计算属性中,我们知道在 Vue 中 每个计算属性都是一个函数。

举个例子:

1
computed: mapState([ 'foo'])

会被映射为:

1
2
3
4
5
6
computed: {
foo() {
return this.$store.state.foo
}
}
}

mapGetters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
if (__DEV__ && !isValidMap(getters)) {
console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
}
normalizeMap(getters).forEach(({ key, val }) => {
// The namespace has been mutated by normalizeNamespace
val = namespace + val
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if (__DEV__ && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
return this.$store.getters[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})

mapGetters 方法会将 store 中的 getter 映射到局部计算属性中。它的实现也和 mapState 类似,区别在于它的 val 只能是字符串,而且会检查 val in this.$store.getters 的值,如果为 false 会报错

举例:

1
2
3
4
5
6
computed: {
...mapGetters('cart', {
a: 'a',
b: 'b'
})
},

会被映射为:

1
2
3
4
computed: {
a: this.$store.getters['cart/a'],
b: this.$store.getters['cart/b'],
}

mapMutations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
if (__DEV__ && !isValidMap(mutations)) {
console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object')
}
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation (...args) {
// Get the commit method from store
let commit = this.$store.commit
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit
}
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})

mapMutations会将 store 中的 commit 方法映射到组件的 methods 中。

举例:

1
2
3
4
methods: {
...mapMutations(['bar']),
...mapMutations('c1oudust', ['bar2'])
}

会被映射为:

1
2
3
4
5
6
7
8
methods: {
bar(...args) {
return this.$store.commit.apply(this.$store, ['bar'].concat(args))
},
bar2(...args){
return this.$store._modulesNamespaceMap.['c1oudust/'].context.dispatch.apply(this.$store, ['bar2'].concat(args))
},
}

mapActions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
if (__DEV__ && !isValidMap(actions)) {
console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
}
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
// get dispatch function from store
let dispatch = this.$store.dispatch
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
dispatch = module.context.dispatch
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})

mapActions的实现几乎和 mapMutations 一样,唯一差别就是映射的是 storedispatch 方法。

举例:

1
2
3
methods: {
...mapMutations(['baz']),
}

会被映射为:

1
2
3
4
5
6
methods: {
bar(...args) {
return this.$store.dispatch.apply(this.$store, ['bar'].concat(args))
}
}

实现一个简单的Vuex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 用于存储vue对象
let Vue;
class Store {
constructor(options = {}) {
// 使用$$state 和 get set 避免直接访问state
// 对应源码 store.js 中 resetStoreVM方法的一部分
this._vm = new Vue({
data:{
$$state: options.state
}
});
// 保存vuex属性对象
this._mutations = options.mutations;
this._actions = options.actions;
this.getters = {};
options.getters && this.handleGetter(options.getters);
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
}
get state(){
return this._vm._data.$$state;
}

set state(v){
console.error("can't set");
}

commit(type,payload){
let entry = this._mutations[type];
if(!entry){
console.error('unknow mutation type');
return;
}
entry(this.state,payload);
}
dispatch(type,payload){
let entry = this._actions[type];
if(!entry){
console.error('unknow action type');
return;
}
entry(this,payload);
}
// 将getter变成响应式
// 对应源码 store.js 中 resetStoreVM方法的一部分
handleGetter(getters){
// Object.keys(getters).map(key => {
// Object.defineProperty(this.getters,key,{
// get:() => getters[key](this.state)
// })
// })

// 使用proxy
let state = this.state;
let handler = {
get (target, key, receiver) {
if (typeof target[key] !== 'function') {
return;
}
return target[key](state)
},
};
this.getters = new Proxy(getters, handler);
}
}

// 对外暴露install方法,在Vue.use(vuex)的时候会调用
const install = (_Vue) => {
Vue = _Vue;
// 挂载 $store,全局混入的方式,对应源码 mixin.js
Vue.mixin({
beforeCreate(){
if(this.$options.store){
Vue.prototype.$store = this.$options.store
}
}
});
}
export default {Store,install}