學習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內部的實現感受滿滿的基礎,對於平時知識點的複習以及理解完源碼對於平時項目的使用仍是頗有幫助的,最後有些不正確的地方但願能獲得大佬們的指正。