20190221
请简述一下vuex实现原理
对vuex基础概念有不懂的可以点这里
vuex实现原理我们简单过一遍源码 地址 https://github.com/vuejs/vuex
首先我们例出几个问题进行思考
- store是怎么注册的?
- mutation,commit 是怎么实现的?
- 辅助函数是怎么实现的?
store是怎么注册的
看了下面的源码就很清楚了, 我们看到vuex在vue 的生命周期中的初始化钩子前插入一段 Vuex 初始化代码。给 Vue 的实例注入一个 $store
的属性,这也就是为什么我们在 Vue 的组件中可以通过 this.$store.xxx
访问到 Vuex 的各种数据和状态
// 源码位置 https://github.com/vuejs/vuex/blob/665455f8daf8512e7adbf63c2842bc0b1e39efdb/src/mixin.js
export default function (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
}
}
}
mutations,commit 是怎么实现的
// 源码位置 https://github.com/vuejs/vuex/blob/665455f8daf8512e7adbf63c2842bc0b1e39efdb/src/store.js#L417
function registerMutation (store, type, handler, path = []) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler(getNestedState(store.state, path), payload)
})
}
registerMutation 是对 store 的 mutation 的初始化,它接受 4 个参数,store为当前 Store 实例,type为 mutation 的 key,handler 为 mutation 执行的回调函数,path 为当前模块的路径。mutation 的作用就是同步修改当前模块的 state ,函数首先通过 type 拿到对应的 mutation 对象数组, 然后把一个 mutation 的包装函数 push 到这个数组中,这个函数接收一个参数 payload,这个就是我们在定义 mutation 的时候接收的额外参数。这个函数执行的时候会调用 mutation 的回调函数,并通过 getNestedState(store.state, path) 方法得到当前模块的 state,和 playload 一起作为回调函数的参数
我们知道mutation是通过commit来触发的,这里我们也来看一下commit的定义
// 源码位置 https://github.com/vuejs/vuex/blob/665455f8daf8512e7adbf63c2842bc0b1e39efdb/src/store.js#L82
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]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
this._subscribers.forEach(sub => sub(mutation, this.state))
if (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
commit 支持 3 个参数,type 表示 mutation 的类型,payload 表示额外的参数,根据 type 去查找对应的 mutation,如果找不到,则输出一条错误信息,否则遍历这个 type 对应的 mutation 对象数组,执行 handler(payload) 方法,这个方法就是之前定义的 wrappedMutationHandler(handler),执行它就相当于执行了 registerMutation 注册的回调函数
辅助函数
辅助函数的实现都差不太多,这里只讲解mapState
// 源码地址 https://github.com/vuejs/vuex/blob/665455f8daf8512e7adbf63c2842bc0b1e39efdb/src/helpers.js#L7
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) {
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 转换成由 {key, val} 对象构成的数组,接着调用 forEach 方法遍历这个数组,构造一个新的对象,这个新对象每个元素都返回一个新的函数 mappedState,函数对 val 的类型判断,如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的 state 和 getters 作为参数,返回值作为 mappedState 的返回值;否则直接把 this.$store.state[val]
作为 mappedState 的返回值
为了更直观的理解,我们看下最终mapState的效果
computed: mapState({
name: state => state.name,
})
// 等同于
computed: {
name: this.$store.state.name
}
关于JS每日一题
JS每日一题可以看成是一个语音答题社区
每天利用碎片时间采用60秒内的语音形式来完成当天的考题
群主在次日0点推送当天的参考答案
- 注 绝不仅限于完成当天任务,更多是查漏补缺,学习群内其它同学优秀的答题思路
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。