mutations vuex 调用_Vuex源码解析
“ 为方便理解,本文中提及的
store
为Store
的实例,promise
为Promise
的实例
源码的大致实现流程如下图:
在正式阅读Vuex
源码之前,我们先实现一个简易版的Vuex
来帮助我们理解
“ 本文源代码:
- href="https://github.com/wangkaiwd/vuex-implement/blob/master/src/myVuex/index.js">简易版Vuex
- href="https://github.com/wangkaiwd/vuex-source-study">注释版Vuex源码
Vuex
使用
“
official documentation
核心配置项:
state
getters
mutations
actions
使用步骤:
import App from './App.vue'
import Vue from 'vue'
// 1. import
import Vuex from 'vuex'
Vue.use(Vuex)
// 2. Vue.use
// 3. inject root Vue instance
const store = new Vuex.Store({state: {},getters: {},mutations: {},actions: {}
})
new Vue({el: '#app',store,render: (h) => h(App)
}
Vuex
的install
方法
Vuex
的使用方式:
- 引入
Vuex
Vue.use(Vuex)
new Vuex.Store
创建Vuex
中Store
的实例- 在
Vue
根实例中作为配置项注入
Vue.use
方法的参数要求时一个函数或者具有install
方法的对象,由上述的使用步骤1~3
可以得出,Vuex
会默认导出一个具有install
方法以及Store
类的对象,代码如下:
// myVuex/index.js
const install = (Vue) => {};class Store {constructor (options) {this.options = options;}
}const Vuex = { install, Store };export default Vuex;
步骤 4 中,我们将store
注入到了Vue
的根实例的选项中,组件中便可以这样使用:
// App.vue
<template><div id="app"><h3>{{$store.state.age}}</h3></div>
</template>
为了能让Vue
的所有子组件都能通过$store
来访问到store
,进而方便的获取store
的属性和方法,Vuex
采用Vue.mixin
将store
在beforeCreate
钩子中进行全局混入:
const install = (Vue) => {Vue.mixin({// 实例初始化后立即同步调用,在数据检测和事件/watcher设置之前beforeCreate () {const { store } = this.$options;if (store) {this.$store = store;} else { // 子组件在渲染的时候会获取父组件的 $store(组件会从上到下进行渲染)this.$store = this.$parent && this.$parent.$store;}}});
};
这样我们便能在所有的注入store
配置的根组件及其所有子组件中使用$store
响应式的state
需要注意的是: 直接为Vue
的实例添加属性,该属性是不具备响应性 的。
此时state
虽然可以获取到,但是由于并没有提前在data
中定义,所以并不是响应式的,即在state
发生变化时,视图并不会随之更新。为了让state
具有响应式,我们在Vuex
内部创建了一个新的Vue
实例,并将state
作为实例的data
中的属性,保持其响应性
class Store {constructor (options) {const { state} = options;// 执行Vue.use会执行install方法,会将全局的Vue赋值为Vue实例// 保证state具有响应性this._vm = new Vue({data: { state }});}// 属性会被定义在实例的原型上// this.state = this._vm.state// 每次都会获取到最新的this._vm.stateget state () {return this._vm.state;}
}
Vuex
与全局变量一个最大的局别在于:Vuex
中store
的state
是响应式的,在state
发生变化时可以保证视图有效更新
mutation
同步更改state
接下来我们尝试更改store.state.age
的值。
在Vuex
中,我们不能直接修改store.state
的值,而是必须要commit
一个mutation
,然后通过mutation
来修改state
。用法如下:
<template><div id="app"><h3>{{$store.state.age}}</h3><button @click="onAdd">add age</button></div>
</template><script>export default {name: 'App',components: {},methods: {onAdd () {this.$store.commit('add', 1);}}};
</script>
// store/index.js
export default new Vuex.Store({// ...mutations: {add (state, payload) {state.age = state.age + payload;}}// ...
});
为了方便遍历对象,我们可以实现一个forEach
方法:
// object iterate
const forEach = (obj, cb) => {Object.keys(obj).forEach((key) => {cb(key, obj[key], obj);});
};
要通过commit
方法更新state
,需要在Store
类初始化的时候,先缓存所有的mutations
,然后通过store
的commit
方法,传入对应的key
来执行mutations
中对应的函数,并且传入state
以及commit
调用时的参数payload
,方便更新store
的state
:
class Store {constructor (options) {const { state, mutations } = options;// 执行Vue.use会执行install方法,会将全局的Vue赋值为Vue实例// 保证state具有响应性this._vm = new Vue({data: { state }});this.mutations = {};forEach(mutations, (key, mutation) => {this.mutations[key] = (payload) => {// this.state是不能被更改的// 但是这里我们将this._vm.state的地址赋值给了参数state,// 之后我们更改的是this._vm.state地址对应的堆内存,而该值是响应式的mutation(this.state, payload);};});}// 属性会被定义在实例的原型上// this.state = this._vm.state// 每次都会获取到最新的this._vm.stateget state () {return this._vm.state;}// 通过commit来修改statecommit (type, payload) {const mutation = this.mutations[type];if (mutation) {mutation(payload);}}
}
action
处理异步任务
在Vuex
中,异步更新state
需要通过dispatch
方法派发一个action
,然后通过action
执行commit
来修改state
:
<template><div id="app"><h3>{{$store.state.age}}</h3><button @click="onAsyncAdd"> async add age</button></div>
</template><script>export default {name: 'App',components: {},methods: {onAsyncAdd () {this.$store.dispatch('asyncAdd', 1);}}};
</script>
export default new Vuex.Store({// ...mutations: {add (state, payload) {state.age = state.age + payload;}},actions: {// const { commit } = store;// this指向不一样// commit()// store.commit()asyncAdd ({ commit }, payload) {// 这里调用commit时,如果不提前指定this的话,this会指向undefinedsetTimeout(() => {commit('add', payload);}, 2000);}},// ...
});
Vuex
中actions
的实现与mutations
类似,不过在mutation
中解构出commit
方法执行时需要我们指定this
指向:
class Store {constructor (options) {// ...this.actions = {};forEach(actions, (key, action) => {this.actions[key] = (payload) => {// action中的第一个参数为Store的实例,可以通过commit来更改state// 也可以通过dispatch来派发另一个actionaction(this, payload);};});// 通过bind返回一个函数赋值为this.commit,该函数内部会通过call执行this.commit,// 并且会将返回函数的参数也传入this.commit// 等号右边 => Store.prototype.commit 原型方法// 等到左边 => store.commit 实例私有方法// this.commit = this.commit.bind(this);}// 通过commit来修改statecommit = (type, payload) => {const mutation = this.mutations[type];if (mutation) {mutation(payload);}};dispatch (type, payload) {const action = this.actions[type];if (action) {action(payload);}}
}
Vuex
中的getters
这里我们已经实现了state
,mutations
,actions
,而有时候我们的state
中的属性过于冗长、或需要计算出一些值,就需要用到getters
:
<template><div id="app"><h2>{{$store.getters.personalInfo}}</h2></div>
</template>
export default new Vuex.Store({state: {age: 10,person: {profile: {job: 'developer',company: 'alipay',name: 'zs'},}},getters: {personalInfo (state) { // 获取个人信息const { profile } = state.person;return Object.keys(profile).reduce((prev, cur) => {return prev + `${cur}: ${profile[cur]}; `;}, '');}}// ...
});
getters
的实现如下:
class Store {constructor() {// do something ...this.getters = {};forEach(getters, (getter, key) => {// 每次取值时都会调用get方法// 而computed方法只会在Object.defineProperty(this.getters, key, {get: () => {return getter(this.state);}});});// do something ...}
}
到这里我们已经实现了一个简易版的Vuex
- 通过
state
来获取数据 - 通过
mutation
同步更改state
- 通过
action
来处理异步行为。
目前的代码只是源码的核心逻辑简化,接下来我们深入解读一下Vuex
源码。
Vuex
源码目录结构
“ 下面我们只摘出源码中的核心代码进行解读,具体细节需要读者去源码中寻找
所有组件都可以访问$store
源码中的install
方法与我们的实现基本上是相同的,代码如下:
// store.js
export function install (_Vue) {Vue = _VueapplyMixin(Vue)
}// mixin
export default function applyMixin (Vue) {Vue.mixin({ beforeCreate: vuexInit })/*** Vuex init hook, injected into each instances init hooks list.*/function vuexInit () {const options = this.$options// store injection// 自上而下将根实例中传入的VuexStore实例store注入到所有组件的实例上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}}
}
子组件在调用beforeCreate
函数时,都会使用其父组件的$store
属性作为自己的$store
属性,而根实例会在实例化时我们手动传入store
属性。这样使每个组件都拥有了$store
属性
依赖收集
在Vuex
中可以将state,actions,mutatoins
等属性根据模块modules
进行划分,方便代码的维护。当然这会生成一个递归的树形结构对象,下面我们看看Vuex
如何优雅的处理递归树形结构数据。
在Store
拿到了用户传入的配置项之后,首先进行的操作是模块收集,其目的是将用户传入的配置项处理为更加方便的树形结构
用户传入:
处理之后:
// store.js
export class Store {constructor (options = {}) {// some code ...this._modules = new ModuleCollection(options);// some code ...}
}// module-collection.js
export default class ModuleCollection {constructor (rawRootModule) {// register root module (Vuex.Store options)this.register([], rawRootModule, false)}register (path, rawModule, runtime = true) {// 格式化用户配置项,并为每个模块原型上添加一些公有方法,方便调用const newModule = new Module(rawModule, runtime);// 处理根模块if (path.length === 0) {this.root = newModule;} else { // 处理子模块// 通过path找到父模块const parent = this.get(path.slice(0, -1));// 将父模块的子模块赋值为当前遍历的模块,key为path的最后一项parent.addChild(path[path.length - 1], newModule);// parent._children[path[path.length-1]] = newModule}// register nested modulesif (rawModule.modules) { // 递归处理子模块forEachValue(rawModule.modules, (rawChildModule, key) => {this.register(path.concat(key), rawChildModule, runtime);});}}
}
到这里我们将配置项处理为了比较方便的结构,并且每个模块也通过Module
类提供了一些原型方法方便直接调用:
{ root:{state: {},_children:{},_rawModule: {},__proto__: {addChild: f,forEachMutation: f,forEachAction: f,...}},
}
模块安装
通过模块收集将用户传入的选项处理为我们方便使用的树形结构后,需要为store
实例添加用户要使用的state, getters, mutations, actions
。
首先我们通过下图大概看一下Vuex
整个安装模块的具体流程
源码中通过installModule
来递归的生成store
实例需要的属性:
export class Store {constructor (options = {}) {// ...// 模块收集this._modules = new ModuleCollection(options);const state = this._modules.root.state;// init root module.// this also recursively registers all sub-modules// and collects all module getters inside this._wrappedGetters// 模块安装installModule(this, state, [], this._modules.root);}
}function installModule (store, rootState, path, module, hot) {// 当path为空数组时,遍历的是根模块const isRoot = !path.length;// 根据path获取当前遍历模块的命名空间namespaceconst namespace = store._modules.getNamespace(path);// register in namespace mapif (module.namespaced) {if (store._modulesNamespaceMap[namespace] && __DEV__) {console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`);}// 在store上存储模块命名空间的映射,key为namespace,value为module// 每个模块都应该有自己单独的命名空间,方便检查命名空间是否重复并提醒用户store._modulesNamespaceMap[namespace] = module;}// set stateif (!isRoot && !hot) {// 根据根state以及path找到对应的父stateconst parentState = getNestedState(rootState, path.slice(0, -1));// path的最后一项为当前处理的模块名const moduleName = path[path.length - 1];store._withCommit(() => {// 保证为state赋值时,值为响应式Vue.set(parentState, moduleName, module.state);// state => this._modules.root.state// store._vm = new Vue({// data: {// $$state: state// }// })// store.state => store._vm._data.$$state// 所以store.state和state即this._modules.root.state指向同一片堆内存空间,堆内存的键值对发生变化时,会同步更新});}// 生成当前模块的state,getters,commit,dispatch// 方便之后在注册mutation,action,getter时使用当前模块的一些属性和方法:// 如在action中可以使用局部的commit,dispatch来调用当前模块的mutation和actionconst local = module.context = makeLocalContext(store, namespace, path);// 为store设置mutationsmodule.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
方法做了以下事情:
- 计算当前模块的命名空间
- 生成
this._module.root.state
并具有响应式 - 注册
mutations
- 注册
actions
- 注册
getters
- 继续递归注册
在执行完成installModule
后,store
大概结构如下:
const store = {"_mutations": {"cart/pushProductToCart": [function handler() {}],},"_actions": {"cart/addProductToCart": [function handler() {}],},
}
需要注意的是,此时并没有将state
和getters
关联到store
中,真正将其关联的方法在resetStoreVM
中:
function resetStoreVM (store, state, hot) {// bind store public gettersstore.getters = {};// reset local getters cachestore._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.// 将getter放到计算属性中computed[key] = partial(fn, store);// store.getters中的属性从store中创建的 vue instance 中获取Object.defineProperty(store.getters, key, {get: () => store._vm[key],enumerable: true // for local getters});});// 通过创建Vue实例,然后将store.state定义在Vue的data中,保证state的响应性// 将getters放入到计算属性中,在从getters中取值时会从store._vm中获取store._vm = new Vue({data: {// 以_或者 $ 开头的属性,将不会被代理在Vue实例上,因为它们可能与Vue内部的属性和API方法发生冲突// 您必须像vm.$data._property一样访问它们$$state: state},computed});Vue.config.silent = silent;// enable strict mode for new vmif (store.strict) {// 启用严格模式,当通过mutation异步更改state时会报错enableStrictMode(store);}
}
在store
中我们使用get
语法来定义state
:
class Store {// ...get state () {return this._vm._data.$$state;}// ...
}const state = this._modules.root.state;store._vm = new Vue({data: {// 以_或者 $ 开头的属性,将不会被代理在Vue实例上,因为它们可能与Vue内部的属性和API方法发生冲突// 您必须像vm.$data._property一样访问它们$$state: state},computed
});
这样我们获取store.state
的值时,相当于从this._modules.root.state
中获取值,通过Vue
当中间层,实现了state
的响应性,保证数据和视图的同步更新。
Store
提供的方法
Store
中提供的最常用的方法是commit
和dispatch
,分别用来提交mutation
和派发action
。它们与state
和组件之间的关系如下:
commit
commit
方法的主要逻辑是根据传入的type
来执行对应的所有mutations
中用户传入的函数
commit (_type, _payload, _options) {// check object-style commitconst {type,payload,options} = unifyObjectStyle(_type, _payload, _options);// 插件调用subscribe方法是回调函数的参数const mutation = { type, payload };const entry = this._mutations[type];if (!entry) {if (__DEV__) {console.error(`[vuex] unknown mutation type: ${type}`);}return;}// 用_withCommit包裹来判断是否同步更改statethis._withCommit(() => {// commit时调用mutation,参数为payloadentry.forEach(function commitIterator (handler) {handler(payload);});});// 调用commit更改state时,调用所有插件中订阅的方法this._subscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.forEach(sub => sub(mutation, this.state));
}
上述代码中,我们看到Vuex
并没有直接执行mutations
中的函数,而是通过将执行过程放入函数中,并作为参数传到了_withCommit
方法中。下面我们看看_withCommit
方法做了些什么
class Store {construtor() {// ... some codethis._committing = false;}_withCommit (fn) {const committing = this._committing;this._committing = true;fn();this._committing = committing;}
}// 启用严格模式
function enableStrictMode (store) {// 该操作是十分昂贵的,所以需要在生产环境禁用// 同步深度监听store中state的变化,当state改变没有通过mutation时,会抛出异常store._vm.$watch(function () { return this._data.$$state; }, () => {if (__DEV__) {assert(store._committing, `do not mutate vuex store state outside mutation handlers.`);}}, { deep: true, sync: true });
}
在开启严格模式后,store
将会利用Vue
提供的$watch
方法深度同步监听this._data.$$state
的变化,也就是在store
的state
发生变化时立即触发第二个参数对应的回调函数。
如果mutations
会异步更改state
,那么在异步更改state
之前会先执行this._committing = false
。此时assert(store.__committing)
会由于断言失败,进行提示。当mutations
同步更改state
时,在state
更改完成后,才会将this._committing
更改为false
,assert(store._committing)
会一直断言成功,不会进行提示。
这样在用户通过mutation
异步更改state
就会在控制台报错。
dispatch
Vuex
中通过Promise
来处理异步的action
。在注册action
的时候,会将action
的返回值强行转换为Promise
实例,方便在dispatch
时处理。
// 在注册action时,会将action的返回值通过Promise.resolve(res)处理成promise,并返回
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,会帮用户拼接命名空间。当传入第三个参数 { root: true },调用全局的dispatchdispatch: local.dispatch,// 当前模块的commit, 会帮用户拼接命名空间commit: local.commit,// 当前模块的getters, 会从命名空间中将当前的getter进行分离getters: local.getters,// 通过path获取到当前模块的statestate: local.state,// 全局的gettersrootGetters: store.getters,// 全局的staterootState: store.state}, payload);if (!isPromise(res)) {// 返回值不是Promise的话通过Promise.resolve转换为Promiseres = Promise.resolve(res);}if (store._devtoolHook) {return res.catch(err => {store._devtoolHook.emit('vuex:error', err);throw err;});} else {return res;}});
}
dispatch
的主要思路是执行所有的异步action
(这里的异步action
表示的是返回值为Promise
实例的函数):
Promise.all
处理同一type
的多个action
(没有设置命名空间)- 同一个
type
只有一个action
,直接获取action
执行后的Promise
实例
dispatch
会返回一个新的Promise
实例,该Promise
实例拥有与action
执行后返回的Promise
实例相同的被解决的值value
和被拒绝的原因reason
dispatch (_type, _payload) {// check object-style dispatchconst {type,payload} = unifyObjectStyle(_type, _payload);const action = { type, payload };const entry = this._actions[type];// 执行所有的actions,actions中的函数会被处理成romise返回,当同一type有多个action时,通过Promise.all进行处理// 最终得到的result也是promiseconst result = entry.length > 1? Promise.all(entry.map(handler => handler(payload))): entry[0](payload);// 如果不用处理额外逻辑的话,可以直接将promise进行返回// return result;// 返回一个新的Promise,该Promise被解决的value是result的value,该Promise被拒绝的reason是result失败的reasonreturn new Promise((resolve, reject) => {result.then(res => {// do something ...resolve(res);}, error => {// do something ...reject(error);});});
}
由于dispatch
返回了一个Promise
实例,所以我们可以通过调用它的.then
方法来保证在dispatch
派发的action
的异步逻辑完成后做一些事情:
store.dispatch('type',payload)
.then(() => { // do someting on success},() => { // do something on failure})
动态注册模块
在Store
被实例化之后,我们还可以通过registerModule
来动态的为Store
添加模块:
registerModule (path, rawModule, options = {}) {// path为字符串时将其处理为数组if (typeof path === 'string') path = [path];// 进行模块收集,根据path以及用户传入的选项// 根据path将其放到this._modules.root上this._modules.register(path, rawModule);// 将新加到this._modules.root上的模块通过path安装到store上installModule(this, this.state, path, this._modules.get(path), options.preserveState);// reset store to update getters...// 为store添加新注册的gettersresetStoreVM(this, this.state);
}
模块动态注册与store
首次处理用户传入的配置项的逻辑完全相同,只不过此时要指定path
:
- 通过
this._modules.register
进行模块收集,转换树形结构 - 将树形结构内容安装到
store
中 - 通过
resetStoreVM
将所有store.getters
重新定义到Vue
实例的computed
属性中
此时,将会成功的为store
重新注册一个新的模块,用户可以成功的访问它的state
,并调用commit
和dispatch
方法来触发mutation
和action
插件机制
Vuex
中的插件会作为函数传入到plugins
选项中:
const myPlugin = store => {// 在store被初始化的时候被调用store.subscribe((mutation, state) => {// 在每次执行mutation之后调用// mutation的格式为 `{ type, payload }`.})
}const store = new Vuex.Store({// ...plugins: [myPlugin]
})
plugins
值为数组,而数组中的每一项即为Vuex
的插件。其本质上就是一个函数,只不过函数会接受一个参数,该参数为Store
的实例,插件的编写者可以调用实例中的方法和属性。
下面我们通过编写一个简化版的logger
插件来学习plugins
的相关源码:
function logger (store) {let prevState = JSON.parse(JSON.stringify(store.state));// 每次修改state时分别打印之前记录前一次和下一次的statestore.subscribe((mutation, state) => {console.log('prevState', prevState);const nextState = JSON.parse(JSON.stringify(state));console.log('nextState', nextState);prevState = nextState;});
}const store = new Vuex.Store({// ...plugins: [logger]
})
我们整理一下源码中有关插件的代码:
class Store {constructor(options) {const {plugins = [], // 配置项中的插件选项,默认值为空对象strict = false} = options;this._subscribers = [];// 依次执行插件数组中的每个函数,参数为Store实例this,可以调用store的属性和方法plugins.forEach(plugin => plugin(this));}subscribe (fn, options) {return genericSubscribe(fn, this._subscribers, options);}commit (_type, _payload, _options) {// some code ...// 调用commit更改state时,调用所有插件中订阅的方法this._subscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.forEach(sub => sub(mutation, this.state));// some code ...}
}function genericSubscribe (fn, subs, options) {// 如果fn在subs中不存在,options中传入{ prepend: true }会将fn放到subs的第一项// 否则会将fn放入到subs中的最后一项if (subs.indexOf(fn) < 0) {options && options.prepend? subs.unshift(fn): subs.push(fn);}// 会返回取消订阅(unsubscribe)函数,将fn从subs中删除,这样在调用mutation的时候就不会触发fnreturn () => {const i = subs.indexOf(fn);if (i > -1) {subs.splice(i, 1);}};
}
在store
的constructor
中,会执行plugins
中传入的每一个函数,并将store
实例作为参数传入。
在插件中调用store.subscribe(fn)
会为_subscribers
数组添加fn
到最后一项,并返回一个取消订阅函数,执行后会将fn
从_subscribers
中删除。
之后在调用store
的commit
方法时,会执行_subscribers
数组中的所有方法,并传入参数mutation
以及store.state
,这样用户可以通过mutation
拿到当前{ type , payload }
以及调用commit
方法更新后的store.state
到这里我们便实现了一个简单的logger
插件,并且结合插件的具体实现理解了plugins
相关的源码。
辅助函数
由于通过$store
属性获取state
以及调用mutation
和action
的代码比较冗余,Vuex
为了简化用户在组件中使用state,getters,dispatch,commit
,提供了一系列的辅助函数来帮我们少写一些代码:
// 从`vuex`中引入
import { mapState } from 'vuex'export default {// ...computed: mapState({// arrow functions can make the code very succinct!count: state => state.count,// 与`state => state.count`的写法作用相同countAlias: 'count',// to access local state with `this`, a normal function must be usedcountPlusLocalState (state) {return state.count + this.localCount}}),mounted() {// 使用store中的stateconsole.log(this.count);console.log(this.countPlusLocalState);}
}
这里以mapState
为例,来看下Vuex
中辅助函数的源码实现:
// 处理命名空间namespace和map的一些可能情况,并且在处理之后将namespace和map传递给回调函数fn
function normalizeNamespace (fn) {return (namespace, map) => {// 命名空间是选传的,如果命名空间不是字符串,那么说明只传了一个参数,将变量往后移,并且命名空间为''if (typeof namespace !== 'string') {map = namespace;namespace = '';} else if (namespace.charAt(namespace.length - 1) !== '/') {// 传入的命名空间如果没有/,帮用户补全namespace += '/';}// fn会通过命名空间以及map会返回一个对象,对象大概像这样:// res = {// age() {// // 当前命名空间的state中的age// return state.age// }// }return fn(namespace, map);};
}// 将用户传入的map统一处理为[{key,val}]的格式
function normalizeMap (map) {// 不是数组或对象的话返回空数组if (!isValidMap(map)) {return [];}// 将数组和对象统一转换为数组// 数组: ['name','age'] => [{key:'name', val: 'name'}, {key:'age', val: 'age'}]// 对象: {a: 'name', b: 'age'} => [{key: 'a', val: 'name'}]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];return module;
}
function installModule (store, rootState, path, module, hot) {// some code ...// 根据path获取当前遍历模块的命名空间namespaceconst namespace = store._modules.getNamespace(path);// register in namespace mapif (module.namespaced) {if (store._modulesNamespaceMap[namespace] && __DEV__) {console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`);}// 在store上存储模块命名空间的映射,key为namespace,value为module// 每个模块都应该有自己单独的命名空间,方便检查命名空间是否重复并提醒用户// 也方便之后在辅助函数中通过命名空间来获取到其对应的模块store._modulesNamespaceMap[namespace] = module;}// 为module.context赋值,方便之后在辅助函数中从module通过context来获取当前模块的state,getters,dispatch,actionconst local = module.context = makeLocalContext(store, namespace, path);// some code ...
}export const mapState = normalizeNamespace((namespace, states) => {const res = {};// [{key:'name', val: 'name'}, {key:'age', val: 'age'}]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;}// 当前命名空间模块的statestate = module.context.state;// 当前命名空间模块的gettersgetters = module.context.getters;}return typeof val === 'function'? val.call(this, state, getters): state[val];};});// res = {// age() {// // 当前命名空间的state中的age// return state.age// }// }return res;
});
mapState
的核心逻辑如下:
- 处理用户传入的
namespace
和map
,为namespace
补充/
,以及处理没有传入namespace
的情况 - 通过序列化后的
namespace
将map
组合成Vue
中计算属性支持的对象格式 - 将处理好的对象返回
我们可以将代码简化一下,使用伪代码来看一下mapState
的实际流程:
function mapState(namesapce,map) {// 处理命名空间,并将map转换为Vue中computed支持的对象格式// ...return {age() {// 通过命名空间获取到对应模块的state,然后取到map中的属性返回// ...return state.age}}
}
mapState(['a','b'], ['age'])
结语
到这里,我们已经基本讲解完了Vuex
的核心源码。当然源码中还有很多文中未提到的知识点,需要小伙伴们真正打开源码去探索,希望笔者的分享能对小伙伴们阅读源码有一些帮助和启发。
mutations vuex 调用_Vuex源码解析相关推荐
- mutations vuex 调用_Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)...
前言 Vuex源码系列不知不觉已经到了第六篇.前置的五篇分别如下: 长篇连载:Vuex源码学习(一)功能梳理 长篇连载:Vuex源码学习(二)脉络梳理 作为一个Web前端,你知道Vuex的instal ...
- vuex 源码分析_Vuex源码解析
写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 可能会有理解存在偏差的地方,欢迎提issue指出,共同 ...
- action mutation 调用_Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)...
module与moduleCollection你一定要会啊!Vuex源码学习(五)加工后的module 在组件中使用vuex的dispatch和commit的时候,我们只要把action.mutati ...
- mutations vuex 调用_Vuex之理解Mutations的用法实例
1.什么是mutations? 通俗的理解mutations,里面装着一些改变数据方法的集合,这是Veux设计很重要的一点,就是把处理数据逻辑方法全部放在mutations里面,使得数据和视图分离. ...
- vuex 源码分析_Vuex源码解析(一):Module初始化
注册 install -> applyMixin->vuexInit 这里只分析Vue2的注册,因为Vue1我也没用过. 其实就是调用了Vue的静态方法mixin.静态方法mixin是全局 ...
- mutations vuex 调用_Vuex中mutations和actions的区别
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化 Mutation 更改 Vuex 的 sto ...
- mutations vuex 调用_Vuex的mutations与actions使用详解
这次给大家带来Vuex的mutations与actions使用详解,mutations与actions使用的注意事项有哪些,下面就是实战案例,一起来看一下. 区分 actions 和 mutation ...
- mutations vuex 调用_Vuex中的Mutations的具体使用方法
在 Vuex 中 store 数据改变的唯一方法就是提交 mutations.mutations里面装着一些改变数据方法的集合,这是Vuex 设计很重要的一点,就是把处理数据逻辑方法全部放在 muta ...
- mutations vuex 调用_Vuex的mutations与actions使用详解 -
这次给大家带来vuex使用步骤详解,vuex使用的注意事项有哪些,下面就是实战案例,一起来看一下.vuex是一个专门为vue.js设计的集中式状态管理架构.状态?我把它理解为在data中的属性需要共享 ...
最新文章
- 加入知识星球(永久免费)
- 首度揭秘:腾讯敏捷研发和极速交付破局之道
- Codeforces Global Round 12 E. Capitalism 差分约束
- python中代码段的标志是什么车_请问这段Python代码是什么意思?
- Sinclair 的ZX Spectrum 年逾三十
- Hadoop集群搭建之问题锦集
- 面向过程(或者叫结构化)分析方法与面向对象分析方法到底区别
- 使用TensorFlow.js的AI聊天机器人一:检测文本中的情绪
- JavaScript学习总结(9)——JS常用函数(一)
- 华容道与数据结构 (续 3)
- 机器学习--组合分类方法之AdaBoost算法
- android imagebutton 动画,Android中ImageButton的三种点击效果—点击变化,点一次换一张,逐帧动画的实现...
- 信息系统项目管理---第九章 项目人力资源管理
- 牛散村期货:3月春风生 第一周非农财经简阅
- 华为交换机初始化_华为交换机启动配置命令详解大全
- 贾俊平统计学思维导图- 第一章 导论
- 递归概述与递归能解决的问题和规则 [数据结构][Java]
- 一加6可以刷的rom_一加OnePlus 6刷机包_线刷包_救砖包_官方ROM包_固件包下载- 线刷宝ROM中心...
- 同步时钟之hwclock命令(硬件-系统,系统-硬件)
- 大数据+物联网智能交通系统
热门文章
- Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx)
- 网络合作伙伴通信协议指南:NETWORK ASSOCIATES GUIDE TO COMMUNICATIONS PROTOCOLS
- python 客户端_Python一个简单的通信程序(客户端 服务器)
- 我的世界服务器怎么修改矿物,我的世界怎么设置自定义矿物
- Yarn的资源调度与隔离
- 一些特殊的电脑快捷键
- java选择排序算法实现
- 电脑桌面游戏_二次元游戏的高人气角色,不管男女都有一个特点,是巧合吗?...
- java excel自动保存_比POI好用的EasyExcel简单使用记录
- Vue 3.0 Beta