文接上回 Vuex源码阅读小笔记(一)
看辅助函数实现前先看看normalizeNamespace,isValidMap,normalizeMap,getModuleByNamespace这几个函数的作用是什么
/* 接收一个函数作为参数,并返回一个函数
* 这里要做的是处理两种情况,传递了命名空间的和没有传递命名空间的
* 没有传递命名空间的就交换参数,传递了命名空间的不是以/结尾,就添加上
*/
function normalizeNamespace(fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
// 是否是数组或对象
function isValidMap(map) {
return Array.isArray(map) || isObject(map)
}
/*
* 传入的参数标准化
* [a,b,c]=>[{key:a,val:a},{key:b,val:b},{key:c,val:c}]
* {a:1,b:2,c:3}=>[{key:a,val:1},{key:b,val:2},{key:c,val:3}]
*/
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] }))
}
// 获取命名空间模块
function getModuleByNamespace(store, helper, namespace) {
const module = store._modulesNamespaceMap[namespace]
if (__DEV__ && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
function normalizeNamespace(fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
mapState
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 不是数组或对象
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() {
// 取得根模块的state和getters
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
// 设置了命名空间,获取当前命名空间下的state和getters
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]
}
res[key].vuex = true
})
return res
})
所以像我们使用mapState就会转换成下面这样
computed: {
...mapState({
checkoutStatus: state => state.cart.checkoutStatus
})
}
// 没有命名空间
computed: {
checkoutStatus: this.$store.state.checkoutStatus
}
// 有命名空间
computed: {
checkoutStatus: this.$store._modulesNamespaceMap[namespace/].context.checkoutStatus
}
mapActions和mapMutations
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) {
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
})
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) {
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
})
mapActions和mapMutations实现都是类似的,使用后都会转为下面的格式
methods: {
...mapActions(['increment'])
...mapActions({
add: 'increment'
})
...mapMutations('namespace', ['increment'])
}
methods: {
increment(...args){
return this.$store.dispatch.apply(this.$store, [increment].concat(args))
}
add(...args){
return increment.apply(this, [dispatch].concat(args))
}
increment(...args){
return this.$store._modulesNamespaceMap['namespace'].context.dispatch.apply(this.$store, ['increment'].concat(args))
}
}
}
mapGetters
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 }) => {
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]
}
res[key].vuex = true
})
return res
})
最终的mapGetters会转换成
computed: {
...mapGetters('cart', {
products: 'cartProducts'
})
},
computed: {
products: this.$store.getters['cart/cartProducts'],
}
在Vue中使用Vuex的原理是什么
我们在Vue中通过Vue.use(Vuex)从而使用Vuex的
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
Vue.use的作用是安装一个插件,如果插件是一个对象,必须提供一个install方法。如果插件是一个函数,则这个函数会被作为install方法。所以我们去源码里面找install方法。
let Vue
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,然后用这个全局变量Vue跟传进来的Vue比较,如果存在且相等,说明已经安装过了,这也是单例模式的实践,使用单例模式来确保Store的唯一性。如果说在这里没用到单例模式,不小心在后面又安装了一次,会为当前的Vue实例重新注入一个新的 Store,也就是说你中间的那些数据操作全都没了,一切归 0。
接下来看applyMixin函数
export default function (Vue) {
// 获取版本
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// Vue.mixin全局混入挂载Store
Vue.mixin({ beforeCreate: vuexInit })
// 兼容1的写法
} else {
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
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) {
this.$store = options.parent.$store
}
}
}
applyMixin函数的作用就是往每个Vue实例组件在beforeCreate声明周期中注入同一个Store实例,所以也就是在组件中能this.$store的原因了
最后
Vuex源码阅读小笔记已经写完了,看完后的想法是Vuex的实现其实并不难,在这种当组件非常多、组件间关系复杂、且嵌套层级很深的时候,将共享的数据抽出来、放在全局,按照一定的规则去存取数据的这种思想更值得学习,而且Vuex源码的阅读不难,是适合推荐去看的。