getter方法的作用 vuex_vuex原来这么简单,带你了解vuex!
点击 上方蓝字 关注我们
生成本地的dispatch、commit、getters和state。
主要作用就是抹平差异化,不需要用户再传模块参数 。
该文内容比较干,请耐心看。。。
1.vuex的原理 每个组件在beforeCreate的生命周期中都混入(Vuex.mixin)同一个Store实例作为属性$store。<template>
num1 {{$store.state.num1}}
template
我们看上面的代码,其实$store.state.num1源码是这样的
class Store { get state() { return this._vm.data.$$state }}
vm.$store._vm._data.$$state.num1 //其实就是这个vm.$store._vm._data.$$state //这个部分是响应式的
如何实现响应式呢?其实很简单就是new Vue()
2.Vuex的源码解读---原理
我们可以看下源码。
function resetStoreVM (store, state, hot) { // 省略若干代码 store._vm = new Vue({ data: { $$state:state }, computed }) //省略代码}
这里的state就是我们定义的state,这里的computed就是处理后的getters,而class Store 上的函数都是围绕修改 vm.$store._vm.data.$$state和computed(getter) 服务的。
3.Vuex是如何安装的源码
我们要了解到Vuex对象其实就是Vue的一个插件,我们用Vue.use(Vuex)来使用它。
我们来看看Vue.use的源码:
function initUse(Vue) { Vue.use = function (plugin) { var installedPlugins = (this._installedPlugins||(this._installedPlugins = [])) // 如果已经存在,则直接返回this(Vue) if (installedPlugins.indexOf(plugin)>-1) { return this } // additional parameters var args = toArray(arguments, 1) // 把this 数组第一项 args.unshift(this) if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) }else if (typeof plugin === 'function') { plugin.apply(null, args) } // 添加到已安装的插件 installedPlugins.push(plugin) return this }}
我们来看一下plugin.install是个什么东东,也就是Vuex中install函数
export function install (_Vue) { if (Vue && _Vue) { return } Vue = _Vue applyMixin (Vue)}
我们需要继续看一下,applyMixin这个函数做了什么,我们可以猜测一下,Vuex的原理就是让每个组件都具有同一个$store 。所以,applyMixin 应该负责初始化,将每个组件在beforeCreae的时候注入同一个store
export defalut function (Vue) { const version = Number(Vue.version.split('.')[0]) if (version >= 2) { Vue.mixin({beforeCreate: vuexInit}) }else { // 1.x 的版本。忽略 } //Vuex init hook ,inject into each instnce init hook list 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 } }}
下面我们可以验证一下,对不对!
const vm = new Vue({ el: '#app', store, render: h=>h(App)})console.log(vm.$store === vm.$children[0].$store) // trueconsole.log(vm.$vm === vm.$children[0].children[0].$store) //trueconsole.log(vm.$vm === vm.$children[0].children[1].$store) //true
答案呼之欲出了。
4.Vuex的构造函数源码解读
export class Store { constructor(options = {}) { // 暂时省略代码 } if (!Vue && typeof window !== 'undefined' && window.Vue) { install (window.Vue) }}
这里可以从我们定义的new Vuex (options) 取出 plugins 和
strict
参数
const { plugins = [], strict = false} = options // 插件默认是空数组,// 严格模式默认是false
我们都知道Vuex的actions、mutations、getters等变量,这就要求在声明Store实例对象的是后需要声明一些内部变量来存放它们。
// store internal state// store 实例对象 内部的 statethis._committing = false// 用来存放处理后的用户自定义的actoinsthis._actions = Object.create(null)// 用来存放 actions 订阅this._actionSubscribers = []// 用来存放处理后的用户自定义的mutationsthis._mutations = Object.create(null)// 用来存放处理后的用户自定义的 gettersthis._wrappedGetters = Object.create(null)// 模块收集器,构造模块树形结构this._modules = new ModuleCollection(options)// 用于存储模块命名空间的关系this._modulesNamespaceMap = Object.create(null)// 订阅this._subscribers = []// 用于使用 $watch 观测 gettersthis._watcherVM = new Vue()// 用来存放生成的本地 getters 的缓存this._makeLocalGettersCache = Object.create(null)
这里使用 Object.create(null) 的原因是因为它没有原型链,也就是Object.create(null).__proto__ 是undefined。
在构造函数中,我们来看两个方法commit 和 dispach ,我们需要保证两个函数里面的this 是store实例。
// bind commit and dispatch to selfconst store = thisconst { dispatch, commit } = thisthis.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)}
还有两个比较重要的初始化,一个是模块初始化,一个是初始化store._vm响应式并且注册_wrappedGetters 作为 computed的属性
// 严格模式,默认是falsethis.strict = strict// 根模块的stateconst state = this._modules.root.state// init root module.// this also recursively registers all sub-modules// and collects all module getters inside this._wrappedGettersinstallModule(this, state, [], this._modules.root)// initialize the store vm, which is responsible for the reactivity// (also registers _wrappedGetters as computed properties)resetStoreVM(this, state)
installModule (this, state, [], this._modules.root)
初始化根模块,并且递归注册所有的子模块,并且收集所有模块的getters放在this._wrappedGetters里
installModule 函数
function installModule (store, rootState, path, module, hot) { // 是根模块 const isRoot = !path.length // 命名空间 字符串 const namespace = store._modules.getNamespace(path) if (module.namespaced) { // 省略代码:模块命名空间map对象中已经有了,开发环境报错提示重复 // module 赋值给 _modulesNamespaceMap[namespace] store._modulesNamespaceMap[namespace] = module } // ... 后续代码 移出来 待读解释}
注册state
// set state// 不是根模块且不是热重载if (!isRoot && !hot) { // 获取父级的state const parentState = getNestedState(rootState, path.slice(0, -1)) // 模块名称 // 比如 cart const moduleName = path[path.length - 1] // state 注册 store._withCommit(() => { // 省略代码:非生产环境 报错 模块 state 重复设置 Vue.set(parentState, moduleName, module.state) })}
最后得到响应式的数据,实例Store.state 比如
{ // 省略若干属性和方法 // 这里的 state 是只读属性 state: { cart: { checkoutStatus: null, items: [] } }}const local = module.context = makeLocalContext(store, namespace, path)
module.context 这个赋值主要是给 helpers 中 mapState、mapGetters、mapMutations、mapActions四个辅助函数使用的。生成本地的dispatch、commit、getters和state。
主要作用就是抹平差异化,不需要用户再传模块参数 。
遍历注册 mutation
module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local)})/** * 注册 mutation * @param {Object} store 对象 * @param {String} type 类型 * @param {Function} handler 用户自定义的函数 * @param {Object} local local 对象 */function registerMutation (store, type, handler, local) { // 收集的所有的mutations找对应的mutation函数,没有就赋值空数组 const entry = store._mutations[type] || (store._mutations[type] = []) // 最后 mutation entry.push(function wrappedMutationHandler (payload) { /** * mutations: { * pushProductToCart (state, { id }) { * console.log(state); * } * } * 也就是为什么用户定义的 mutation 第一个参数是state的原因,第二个参数是payload参数 */ handler.call(store, local.state, payload) })}
遍历注册 action
module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local)})/*** 注册 mutation* @param {Object} store 对象* @param {String} type 类型* @param {Function} handler 用户自定义的函数* @param {Object} local local 对象*/function registerAction (store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []) // payload 是actions函数的第二个参数 entry.push(function wrappedActionHandler (payload) { /** * 也就是为什么用户定义的actions中的函数第一个参数有 * { dispatch, commit, getters, state, rootGetters, rootState } 的原因 * actions: { * checkout ({ commit, state }, products) { * console.log(commit, state); * } * } */ let res = handler.call(store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload) /** * export function isPromise (val) { return val && typeof val.then === 'function' } * 判断如果不是Promise Promise 化,也就是为啥 actions 中处理异步函数 也就是为什么构造函数中断言不支持promise报错的原因 vuex需要Promise polyfill assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) */ if (!isPromise(res)) { res = Promise.resolve(res) } // devtool 工具触发 vuex:error if (store._devtoolHook) { // catch 捕获错误 return res.catch(err => { store._devtoolHook.emit('vuex:error', err) // 抛出错误 throw err }) } else { // 然后函数执行结果 return res } })}
遍历注册 getter
module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local)})/** * 注册 getter * @param {Object} store Store实例 * @param {String} type 类型 * @param {Object} rawGetter 原始未加工的 getter 也就是用户定义的 getter 函数 * @examples 比如 cartProducts: (state, getters, rootState, rootGetters) => {} * @param {Object} local 本地 local 对象 */function registerGetter (store, type, rawGetter, local) { // 类型如果已经存在,报错:已经存在 if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] duplicate getter key: ${type}`) } return } // 否则:赋值 store._wrappedGetters[type] = function wrappedGetter (store) { /** * 这也就是为啥 getters 中能获取到 (state, getters, rootState, rootGetters) 这些值的原因 * getters = { * cartProducts: (state, getters, rootState, rootGetters) => { * console.log(state, getters, rootState, rootGetters); * } * } */ return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }}
resetStoreVM 函数
function resetStoreVM (store, state, hot) { // 存储一份老的Vue实例对象 _vm const oldVm = store._vm // bind store public getters // 绑定 store.getter store.getters = {} // reset local getters cache // 重置 本地getters的缓存 store._makeLocalGettersCache = Object.create(null) // 注册时收集的处理后的用户自定义的 wrappedGetters const wrappedGetters = store._wrappedGetters // 声明 计算属性 computed 对象 const computed = {} // 遍历 wrappedGetters 赋值到 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. /** * partial 函数 * 执行函数 返回一个新函数 export function partial (fn, arg) { return function () { return fn(arg) } } */ computed[key] = partial(fn, store) // getter 赋值 keys 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 // 声明变量 silent 存储用户设置的静默模式配置 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 // 开启严格模式 执行这句 // 用 $watch 观测 state,只能使用 mutation 修改 也就是 _withCommit 函数 if (store.strict) { enableStrictMode(store) } // 如果存在老的 _vm 实例 if (oldVm) { // 热加载为 true if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. // 设置 oldVm._data.$$state = null store._withCommit(() => { oldVm._data.$$state = null }) } // 实例销毁 Vue.nextTick(() => oldVm.$destroy()) }}
好了上面就是我们剖析的构造函数部分了。我们来看一些Vuex.store的一些api实现。
Vuex.Store的实例方法
commit
提交 mutation
commit (_type, _payload, _options) { // check object-style commit // 统一成对象风格 const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } // 取出处理后的用户定义 mutation const entry = this._mutations[type] // 省略 非生产环境的警告代码 ... this._withCommit(() => { // 遍历执行 entry.forEach(function commitIterator (handler) { handler(payload) }) }) // 订阅 mutation 执行 this._subscribers.forEach(sub => sub(mutation, this.state)) // 省略 非生产环境的警告代码 ...}
commit 支持多种方式,比如:
store.commit('increment', { count: 10})// 对象提交方式store.commit({ type: 'increment', count: 10})
unifyObjectStyle函数将参数统一,返回 { type, payload, options }。
dispatch
分发 action
dispatch (_type, _payload) { // check object-style dispatch // 获取到type和payload参数 const { type, payload } = unifyObjectStyle(_type, _payload) // 声明 action 变量 等于 type和payload参数 const action = { type, payload } // 入口,也就是 _actions 集合 const entry = this._actions[type] // 省略 非生产环境的警告代码 ... try { 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) } } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return result.then(res => { try { 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) } } return res })}
replaceState
替换store的根状态,仅用状态合并或者时光旅行调试。
replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state })}
watch
响应式地侦听 fn 的返回值,当值改变时调用回调函数。
/** * 观测某个值 * @param {Function} getter 函数 * @param {Function} cb 回调 * @param {Object} options 参数对象 */watch (getter, cb, options) { if (process.env.NODE_ENV !== 'production') { assert(typeof getter === 'function', `store.watch only accepts a function.`) } return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)}
subscribe
订阅 store 的 mutation
subscribe (fn) { return genericSubscribe(fn, this._subscribers)}// 收集订阅者function genericSubscribe (fn, subs) { if (subs.indexOf(fn) < 0) { subs.push(fn) } return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } }}
subscribeAction
订阅 store 的 action
subscribeAction (fn) { const subs = typeof fn === 'function' ? { before: fn } : fn return genericSubscribe(subs, this._actionSubscribers)}
registerModule
注册一个动态模块
/** * 动态注册模块 * @param {Array|String} path 路径 * @param {Object} rawModule 原始未加工的模块 * @param {Object} options 参数选项 */registerModule (path, rawModule, options = {}) { // 如果 path 是字符串,转成数组 if (typeof path === 'string') path = [path] // 省略 非生产环境 报错代码 // 手动调用 模块注册的方法 this._modules.register(path, rawModule) // 安装模块 installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... // 设置 resetStoreVM resetS
unregisterModule
卸载一个动态模块
/** * 注销模块 * @param {Array|String} path 路径 */unregisterModule (path) { // 如果 path 是字符串,转成数组 if (typeof path === 'string') path = [path] // 省略 非生产环境 报错代码 ... // 手动调用模块注销 this._modules.unregister(path) this._withCommit(() => { // 注销这个模块 const parentState = getNestedState(this.state, path.slice(0, -1)) Vue.delete(parentState, path[path.length - 1]) }) // 重置 Store resetStore(this)}
hotUpdate
热替换新的 action 和 mutation
// 热加载hotUpdate (newOptions) { // 调用的是 ModuleCollection 的 update 方法,最终调用对应的是每个 Module 的 update this._modules.update(newOptions) // 重置 Store resetStore(this, true)}
组件绑定的辅助函数
mapState
为组件创建计算属性以返回Vuex store中的状态。
export const mapState = normalizeNamespace((namespace, states) => { const res = {} // 非生产环境 判断参数 states 必须是数组或者是对象 if (process.env.NODE_ENV !== 'production' && !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 // 传了参数 namespace if (namespace) { // 用 namespace 从 store 中找一个模块。 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] } // 标记为 vuex 方便在 devtools 显示 // mark vuex getter for devtools res[key].vuex = true }) return res})
normalizeNamespace 标准化统一命名空间
function normalizeNamespace (fn) { return (namespace, map) => { // 命名空间没传,交换参数,namespace 为空字符串 if (typeof namespace !== 'string') { map = namespace namespace = '' } else if (namespace.charAt(namespace.length - 1) !== '/') { // 如果是字符串,最后一个字符不是 / 添加 / // 因为 _modulesNamespaceMap 存储的是这样的结构。 /** * _modulesNamespaceMap: cart/: {} products/: {} } * */ namespace += '/' } return fn(namespace, map) }}// 校验是否是map 是数组或者是对象。function isValidMap (map) { return Array.isArray(map) || isObject(map)}/** * Normalize the map * 标准化统一 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] }))}
module.context 这个赋值主要是给 helpers 中 mapState、mapGetters、mapMutations、mapActions四个辅助函数使用的。
getModuleByNamespace
function getModuleByNamespace (store, helper, namespace) { // _modulesNamespaceMap 这个变量在 class Store 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}
mapGetters
为组件创建计算属性以返回 getter 的返回值
export const mapGetters = normalizeNamespace((namespace, getters) => { const res = {} // 省略代码:非生产环境 判断参数 getters 必须是数组或者是对象 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 } // 省略代码:匹配不到 getter return this.$store.getters[val] } // mark vuex getter for devtools res[key].vuex = true }) return res})
mapActions
创建组件方法分发 action
export 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 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})
mapMutations
创建组件方法提交 mutation。 mapMutations 和 mapActions 类似,只是 dispatch 换成了 commit
let commit = this.$store.commitcommit = module.context.commitreturn typeof val === 'function' ? val.apply(this, [commit].concat(args)) : commit.apply(this.$store, [val].concat(args))
createNamespacedHelpers
创建基于命名空间的组件绑定辅助函数
export const createNamespacedHelpers = (namespace) => ({ // bind(null) 严格模式下,napState等的函数 this 指向就是 null mapState: mapState.bind(null, namespace), mapGetters: mapGetters.bind(null, namespace), mapMutations: mapMutations.bind(null, namespace), mapActions: mapActions.bind(null, namespace)})
以上就是我对vuex的简单分析,如果有需要可以贴一份分析过的代码。
扫描关注
老陈说前端
想象不到的干货