学习Vue有一段时间了,感受对于Vuex老是停留在只知其一;不知其二的状态,决定花点时间好好学习研究下Vuex的实现。Vuex的设计思想,借鉴了Redux将数据存放到全局的store,再将store挂载到每一个vue实例组件中,利用Vue.js的数据响应机制来进行高效的派发和状态更新。javascript
我的以为有必要理解这几个知识点对于理解源码有很大的帮助vue
举个例子,假设该模块须要命名空间,根据例子再去摸索源码会有更加不错的帮助java
store/user.jsvuex
modules: {
users: {
namespaced: true,
state: {
username: null,
},
mutations: {
SET_USER(state, payload) {
state.username = payload
}
},
actions: {
// context包含commit, dispatch, localState, localGetters, rootGetters, rootState
FETCH_USER(context, payload) {
}
},
getters: {
GET_USER(localState, localGetters, rootState, rootGetters) {
return localState.username
}
}
}
}
复制代码
store/index.js数组
import Vue from 'vue'
import Vuex from 'vuex'
import user from './user'
Vue.use(vuex);
new Store({
modules: {
user
}
})
复制代码
user.vue闭包
<script>
import { mapGetters, mapActions, mapMutations } from 'vuex';
export default {
computed: {
...mapGetters('user', [
'GET_USER'
])
},
methods: {
...mapActions('user', {
'fetchUser': FETCH_USER,
}),
...mapMutations('user', {
'setUser': SET_USER,
}),
loginByUsername() {
// fetchUser请求
},
loginByDispatch() {
this.$store.dispatch('user/FETCH_USER', {
user: ...,
password: ....,
randomStr: ....,
code: ...
}).then(res => console.log(res))
.catch(err => console.log(err))
.finally()
}
}
}
</script>
复制代码
主要完成了对于一些状态的初始化,_mutations
对象将用于存放模块中的全部mutations,_actions
对象将用于存放模块中的全部actions,_wrappedGetters
用于存放模块中的全部getter, _modulesNamespaceMap
用于存放存在namespaced为true的key-value表,对于module对象进行从新注册:app
// rawRootModule为传入Store中的原生module对象
var ModuleCollection = function ModuleCollection (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false);
};
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
var this$1 = this;
..
var newModule = new Module(rawModule, runtime);
if (path.length === 0) {
this.root = newModule;
} else {
var parent = this.get(path.slice(0, -1));
parent.addChild(path[path.length - 1], newModule);
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, function (rawChildModule, key) {
this$1.register(path.concat(key), rawChildModule, runtime);
});
}
};
复制代码
// Base data struct for store's module, package with some attribute and method
var Module = function Module (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;
var rawState = rawModule.state;
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
复制代码
modules: {
user: {
state: {level: 1}
post: {
state: {level: 2}
}
}
}
复制代码
首先初始化path长度为0,最外层构造出来的module对象即为root, 而后因为存在子模会将user模块
add到root下的_children
,结果为dom
root: {
...
_children: {
user module
}
}
复制代码
而后经过判断存在子模块,则继续进行递归遍历,此时因为上一层的函数没有出栈,经过path.concat(key)
, path为['user', 'post']
,经过ModuleCollection原型中的get
来获取当前模块的父模块函数
// result: ['user']
let parentPath = ['user', 'post'].slice(0, -1);
// root为根模块,最终获取到的为user module
parentPath.reduce((module, key) => module._children, root)
// 将新增模块加入到user module
root: {
...
_children: {
user: {
_children: {
post module
}
}
}
}
复制代码
最终构形成以下post
完成了模块的注册之后,最重要的一句代码是installModule
, 该方法顾名思义就是将注入的modules进行内部的组装, 若是存在子模块经过递归的方法来获取,而且合并path路径。
首先模块分为有命名空间和没有命名空间两块,经过getNamespace
判断获取命名空间,好比path = ['user', 'post', 'report']
, 经过reduce方法首先经过getChild
获取root
下的_children
里的user模块
, 若是namespaced为true,则加上路径,这样一层层下去,全部的模块,子模块内部将会造成相似user
,user/post/
形式的命名空间。
ModuleCollection.prototype.getNamespace = function getNamespace (path) {
var module = this.root;
return path.reduce(function (namespace, key) {
// 获取子模块
module = module.getChild(key);
return namespace + (module.namespaced ? key + '/' : '')
}, '')
};
复制代码
命名空间很重要,以后commit,dispatch, mapMutations, mapActions等一些列操做都是基于此。以后将子module下全部的state所有暴露到根节点下的state,经过使用vue.set将新的属性设置到rootState上, 这个state将来将被用于store.state获取其状态
// set state
if (!isRoot && !hot) {
// 经过path.slice(0, -1)来截取当前module以前的全部父类路径,经过reduce来获取当前模块上一级的父模块
var parentState = getNestedState(rootState, path.slice(0, -1));
var moduleName = path[path.length - 1];
store._withCommit(function () {
Vue.set(parentState, moduleName, module.state);
});
}
复制代码
以后是将注册registerMutation,registerAction,registerGetter,在注册以前,vuex作了巧妙的处理,动态设置当前模块所在的环境
var local = module.context = makeLocalContext(store, namespace, path);
local = {
dispatch,
commit,
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? function () { return store.getters; }
: function () { return makeLocalGetters(store, namespace); }
},
state: {
get: function () { return getNestedState(store.state, path); }
}
});
}
复制代码
经过namespaced来判断设置当前环境下local对象内的dispatch,commit, 若是存在就在dispatch和commit内部加上namespaced前缀,此外还加入了了local.state和local.getter,经过Object.defineProperties来设置访问器属性,当不一样模块内部好比actions,mutations或者getters中的方法进行获取的时候会进行动态获取。好比带有命名空间的模块:
{
user: {
namspaced: true
state: {
username: 1
}
mutations: {
SET_USER(state, payload) {}
},
actions: {
FETCH_USER({dispatch, commit, getters, state, rootGetters, rootState}, payload) {
// ...
}
},
getters: {
GET_USER() {}
}
}
}
复制代码
就是将全部模块内部的mutations平铺到_mutations中造成key-value的键值对,key为namespaced+key。当触发方法的时候会内置local.state,能够在方法的第一个参数获取到内部本身的state
上面例子最后会被平铺成
```javascript
_mutations: {
'user/SET_USER': [wrappedMutationHandler]
}
```
当commit('SET_USER', 1)的时候`SET_USER`的参数第一个参数会去动态获取state的值, 具体获取方式是经过`getNestedState`方法,配合`path`来获取其state。
```javascript
// 例以下面例子,经过reduce首先获取root层,再次遍历获取user层对象数据
path: ['root', 'user']
store.state:
{
root: {
...
user:{
}
}
}
```
复制代码
相似于注册mutation,会将全部模块下的actions平铺到_actions, 上面例子最后会平铺成
_actions: {
'user/FETCH_USER': [wrappedActionHandler]
}
复制代码
因此外部进行dispatch的时候,若是有命名空间须要加上,例如store.dispatch('user/GET_USER',1),内部其实经过key找到_actions内部的entry,而后调用wrappedActionHandler(payload),当触发方法的时候内部一样内置了local.dispatch
,local.commmit
, local.state
,local.getters
,store.getters
, store.state
.
function makeLocalGetters (store, namespace) {
var gettersProxy = {};
var splitPos = namespace.length;
Object.keys(store.getters).forEach(function (type) {
// skip if the target getter is not match this namespace
if (type.slice(0, splitPos) !== namespace) { return }
// extract local getter type
var localType = type.slice(splitPos);
// Add a port to the getters proxy.
// Define as getter property because
// we do not want to evaluate the getters in this time.
Object.defineProperty(gettersProxy, localType, {
get: function () { return store.getters[type]; },
enumerable: true
});
});
return gettersProxy
}
复制代码
一样道理其中,将全部模块下的getters平铺到_wrappedGetters, 当获取不一样模块下的getters的时候会内置local.getters, local.state, store.getters, store.state
store.getters
为什么访问getter属性相似于vue中的computed,缘由就在于将全部getter设置进了vm,而且在访问的时候对于store.getter对象内部的每一个方法名为key的函数设置了访问器属性,当外部进行调用的时候,返回计算属性计算到的结果。
store.state
prototypeAccessors$1 = { state: { configurable: true } }
prototypeAccessors$1.state.get = function () {
return this._vm._data.$$state
};
Object.defineProperties( Store.prototype, prototypeAccessors$1 );
``` 复制代码
这样模块内的基本要素mutation, actions, getters, state等所有注册完了。
这三个方法是为了在vue中更加方便容易地置入以及使用,说白了就是经过命名空间组合的类型分别去_mutations, _actions, store.getters的对象中取对应的value, 因此第一个参数都为命名空间名(若是有命名空间),第二个参数能够是数组的形式也能够是对象的形式,不管哪一种形式,最后都会进行标准化例如
mapGetters('user', [
'GET_USER' => [{key: 'GET_USER', val: 'GET_USER'}]
])
mapGetters('user', {
getUser: 'GET_USER' => [{key: 'getUser', val: 'GET_USER'}]
})
mapMutations('user', [
'SET_USER' => [{key: 'SET_USER', val: 'SET_USER'}]
])
mapMutations('user', {
setUser: 'SET_USER' => [{key: 'setUser', val: 'SET_USER'}]
})
mapActions('user', [
'FETCH_USER' => [{key: 'FETCH_USER', val: 'FETCH_USER'}]
])
mapActions('user', {
fetchUser: 'FETCH_USER' => [{key: 'fetchUser', val: 'FETCH_USER'}]
})
复制代码
经过命名空间获取对应的module, 这样就可以获取到该模块的上下文context
固然还有另外一种写法, 当其val
为function
的时候, 会内置commit, dispatch参数
mapMutations('user', {
setOtherUser: (commit) => {
}
})
mapActions('user', {
fetchOtherUser: (dispatch) => {
}
})
复制代码
最后mutation
会进行commit.apply(this.$store, [val].concat(args))
action
会进行dispatch.apply(this.$store, [val].concat(args))
, state
返回state[val]
, getter
直接返回store.getters[val]
,其中val
为其actions,mutations, getters
方法名。
上面进行dispatch(_type, _payload)以及commit(_type, _payload, _options),其实处理了2件事情:
处理传参
通常传参都是:
commit({
type: 'SET_USER',
payload: 1
}),
复制代码
但也能够
commit('SET_USER', 1)
复制代码
他内部进行对参数的从新组合,若是是对象则type=obj.type; payload=obj
, 若是有options
则options=payload
找到对应方法进行调用
经过key找到对应的方法数组,若是是commit,则遍历数组依次执行数组内的方法,若是是dispatch,则将数组内的全部进行Promise.all
, 返回Promise对象
接下来就能够快乐地使用vuex了,进行dispatch和commit, 以及mapGetters等一系列操做了。
不得不说vuex内部的实现感受满满的基础,对于平时知识点的复习以及理解完源码对于平时项目的使用仍是颇有帮助的,最后有些不正确的地方但愿能获得大佬们的指正。