vuex官方文檔
Vuex是什么?
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化
每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同:
- Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
- 你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
實現簡易版的vuex
先來看下vuex的基本用法
import Vue from 'vue';
import Vuex from 'vuex';
// 1.Vue.use(Vuex); Vuex是一個對象 install方法
// 2.Vuex中有一個Store類
// 3.混入到組件中 增添store屬性Vue.use(Vuex); // 使用這個插件 內部會調用Vuex中的install方法const store = new Vuex.Store({state:{ // -> dataage:10},getters:{ // 計算屬性myAge(state){return state.age + 20}},mutations:{ // method=> 同步的更改state mutation的參數是狀態changeAge(state,payload){state.age += payload; // 更新age屬性}},actions:{ // 異步操作做完后將結果提交給mutationschangeAge({commit},payload){setTimeout(() => {commit('changeAge',payload)}, 1000);}}
});
export default store;
通過用法可以知道:
- Vuex是一個對象,它作為vue的插件,必然有install方法;
- Vuex中有一個Store類,在使用的時候有使用new;
- 需要將store混入到組件中。
于是可以梳理好入口文件
vuex/index.js
import { Store, install } from './store'; // 這個文件是入口文件,核心就是導出所有寫好的方法
export default {Store,install
}
store文件
vuex/store.js
export let Vue;export class Store {}// _vue 是Vue的構造函數
export const install = (_vue) => {// 需要保存Vue,用戶傳入的Vue構造函數Vue = _vue;
}
接下來就是把store掛載到每個組件上面,這樣數據才能互通共享,很顯然,通過Vue.mixin 在Vue生命周期beforeCreate 可以為每個組件注入store;
import applyMixin from "./mixin";
export let Vue;export class Store {}// _vue 是Vue的構造函數
export const install = (_vue) => {// 需要保存Vue,用戶傳入的Vue構造函數Vue = _vue;// 需要將根組件中注入的store 分派給每一個組件 (子組件) Vue.mixinapplyMixin(Vue);
}
vuex/mixin.js
export default function applyMixin(Vue) {// 父子組件的beforecreate執行順序Vue.mixin({ // 內部會把生命周期函數 拍平成一個數組 beforeCreate: vuexInit});
}// 組件渲染時從父=》子function vuexInit() {// 給所有的組件增加$store 屬性 指向我們創建的store實例const options = this.$options; // 獲取用戶所有的選項if (options.store) { // 根實例(只有根實例才會有store屬性)this.$store = options.store;} else if (options.parent && options.parent.$store) { // 兒子 或者孫子....// 后面的每一個都從其父組件拿到storethis.$store = options.parent.$store;}
}
接下來就是處理state,getters,mutations,actions
state實現
export class Store {constructor(options) {const state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state}})}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
首先來處理state,options是用戶傳入的,其中有state,getters,mutations,actions,自然可以在options.state中取到,但是此時state還不是響應式,可以借助new Vue中data的數據是響應式處理這個問題,將state掛載到$$state上,這個屬性是不會被vue暴露出去(可能是內部做了處理)。當我們在組件中去獲取值的時候,比如this.store.state 就走到到了訪問器get state() 就會將整個倉庫的state返回出去,而且數據是響應式的。至于為什么在_vm._data上,需要去看下vue源碼實現。
getters實現
export class Store {constructor(options) {// 1.處理stateconst state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state}})// 2.處理getters屬性 具有緩存的 computed 帶有緩存 (多次取值是如果值不變是不會重新取值)this.getters = {};Object.key(options.getters).forEach(key => {Object.defineProperty(this.getters, key, {get: () => options.getters[key](this.state)})})}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
通過循環用戶傳進來的getters,再通過Object.defineProperty把每一個getter放入store中。不過目前每一次取值都會重新計算,沒有緩存功能,不符合vue計算屬性的用法以及定義。
先來改造下對象遍歷這個方法,因為這個方法后面用的比較多。
vuex/util.js
export const forEachValue = (obj, callback) => {Object.keys(obj).forEach(key => callback(obj[key], key))
}
export class Store {constructor(options) {// 1.處理stateconst state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state}})// 2.處理getters屬性 具有緩存的 computed 帶有緩存 (多次取值是如果值不變是不會重新取值)this.getters = {};forEachValue(options.getters, (fn, key) => {Object.defineProperty(this.getters, key, {get: () => fn(this.state)})})}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
邏輯都是一樣的,接著處理下緩存功能。
export class Store {constructor(options) {// 1.處理stateconst state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)const computed = {};// 2.處理getters屬性 具有緩存的 computed 帶有緩存 (多次取值是如果值不變是不會重新取值)this.getters = {};forEachValue(options.getters, (fn, key) => {// 將用戶的getters 定義在實例上, 計算屬性是如何實現緩存computed[key] = () => fn(this.state);// 當取值的時候執行計算屬性的邏輯,此時就有緩存功能Object.defineProperty(this.getters, key, {get: () => fn(this._vm[key])})})this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state},computed,})}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
computed具有緩存功能,可以在用戶傳入的getters的時候,將用戶的getters 定義在實例上,computed[key] = () => fn(this.state) ,在取值的時候fn(this._vm[key])執行計算屬性的邏輯。vuex的作者真是腦洞大開,鬼才啊,這都能想到。
mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)
對傳入的屬性進行遍歷訂閱
通過commit方法觸發調用。
mutation實現
// 3.實現mutations
this.mutations = {};
forEachValue(options.mutations, (fn, key) => {this.mutations[key] = (payload) => fn(this.state, payload)
})commit = (type, payload) => { //保證當前this 當前store實例this.mutations[type](payload)
}
commit使用箭頭函數是為了保證調用的都是當前實例,一是通過this.commit(type,data),二是在action中被解構使用changeAge({commit},payload){}
actions和dispath也是如此。
完整的Store類
export class Store {constructor(options) {// 1.處理stateconst state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)const computed = {};// 2.處理getters屬性 具有緩存的 computed 帶有緩存 (多次取值是如果值不變是不會重新取值)this.getters = {};forEachValue(options.getters, (fn, key) => {// 將用戶的getters 定義在實例上, 計算屬性是如何實現緩存computed[key] = () => fn(this.state);// 當取值的時候執行計算屬性的邏輯,此時就有緩存功能Object.defineProperty(this.getters, key, {get: () => fn(this._vm[key])})})this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state},computed,})// 3.實現mutationsthis.mutations = {};forEachValue(options.mutations, (fn, key) => {this.mutations[key] = (payload) => fn(this.state, payload)})// 4.實現actionsthis.actions = {};forEachValue(options.actions, (fn, key) => {this.actions[key] = (payload) => fn(this, payload);});}commit = (type, payload) => { //保證當前this 當前store實例this.mutations[type](payload)}dispatch = (type, payload) => {this.mutations[type](payload)}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
完整的store.js
import applyMixin from "./mixin";
import { forEachValue } from './util';
export let Vue;export class Store {constructor(options) {// 1.處理stateconst state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)const computed = {};// 2.處理getters屬性 具有緩存的 computed 帶有緩存 (多次取值是如果值不變是不會重新取值)this.getters = {};forEachValue(options.getters, (fn, key) => {// 將用戶的getters 定義在實例上, 計算屬性是如何實現緩存computed[key] = () => fn(this.state);// 當取值的時候執行計算屬性的邏輯,此時就有緩存功能Object.defineProperty(this.getters, key, {get: () => fn(this._vm[key])})})this._vm = new Vue({data: { // 屬性如果是通過$開頭的 默認不會將這個屬性掛載到vm上$$store: state},computed,})// 3.實現mutationsthis.mutations = {};forEachValue(options.mutations, (fn, key) => {this.mutations[key] = (payload) => fn(this.state, payload)})// 4.實現actionsthis.actions = {};forEachValue(options.actions, (fn, key) => {this.actions[key] = (payload) => fn(this, payload);});}commit = (type, payload) => { //保證當前this 當前store實例this.mutations[type](payload)}dispatch = (type, payload) => {this.mutations[type](payload)}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}// _vue 是Vue的構造函數
export const install = (_vue) => {// 需要保存Vue,用戶傳入的Vue構造函數Vue = _vue;// 需要將根組件中注入的store 分派給每一個組件 (子組件) Vue.mixinapplyMixin(Vue);
}
簡易版的vuex到此完成。接下來就是要處理module。
完整版Vuex實現
我們實現了一個簡易版的Vuex,對state,actions,mutations,getters 進行了功能的實現。但是沒有對modules進行處理,其實modules才是Vuex中最核心并且是最難實現的。
Vuex 允許我們將 store 分割成大大小小的對象,每個對象也都擁有自己的 state、getter、mutation、action,這個對象我們把它叫做 module(模塊),在模塊中還可以繼續嵌套子模塊。
- state: 所有模塊中的state中數據最終都會嵌套在一棵樹上。類似于如下

-
模塊內部的 action、mutation 和 getter 默認可是注冊在全局命名空間的,這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。因此在訂閱mutation 和action時必須存儲在數組中,每次觸發,數組中的方法都要執行。
image
Vuex中可以為每個模塊添加namespaced: true來標記為當前模塊劃分一個命名空間,接下來看下具體怎么實現一個完整的Vuex。
具體實現
總體思路可以分為以下:
- 模塊收集。就是把用戶傳給store的數據進行格式化,格式化成我們想要的結構(樹)
- 安裝模塊。需要將子模塊通過模塊名定義在跟模塊上
- 把狀態state和getters定義到當前的vm上。
模塊收集
import ModuleCollection from './module/module-collection'
export let Vue;export class Store {constructor(options) {const state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)// 1.模塊收集this._modules = new ModuleCollection(options);}
}
ModuleCollection 類的實現
這個類是收集模塊,格式化數據用的,那我們先要知道需要什么樣的格式。
this.root = {_raw: '根模塊',_children:{a:{_raw:"a模塊",_children:{c:{.....}},state:'a的狀態' },b:{_raw:"b模塊",_children:{},state:'b的狀態' }},state:'根模塊自己的狀態'}
最終需要的是這樣一個數結構。
export default class ModuleCollection {constructor(options) {// 注冊模塊 需要用到棧結構數據,[根,a],每次循環遞歸的時候將其入棧。這樣每個模塊可以清楚的知道自己的父級是誰this.register([], options)}register(path, rootModule) {// 格式化后的結果let newModule = { _raw: rootModule, // 用戶定義的模塊_children: {}, // 模塊的兒子state: {} // 當前模塊的狀態}if (path.length === 0) { // 說明是根模塊this.root = newModule} // 用戶在模塊中傳了modules屬性if (rootModule.modules) {// 循環模塊 module模塊的定義 moduleName模塊的名字forEachValue(rootModule.modules, (module, moduleName) => {this.register(path.concat(moduleName), module)})}}
}
第一次進來的時候path是空數組,root就是用戶傳進去的模塊對象;如果模塊有modules屬性,需要循環去注冊這個模塊。path.concat(moduleName) 就返回了[a,c]類似的格式。 接下來看下path不為空的時候
if (path.length === 0) { // 說明是根模塊this.root = newModule
} else {// this.register(path.concat(moduleName), module); 遞歸注冊前會把module 的名放在 path的位this.root._children[path[path.length -1]] = newModule
}
path[path.length -1] 可以取到最后一項,也就是模塊的兒子模塊。這里我們用的是this.root._children[path[path.length -1]] = newModule。這樣寫會把有多層路徑的模塊最后一項也提到和它平級,因此需要確定這個模塊的父級是誰,再把當前模塊掛到父級就okl了
if (path.length === 0) { // 說明是根模塊this.root = newModule
} else {// this.register(path.concat(moduleName), module); 遞歸注冊前會把module 的名放在 path的位// path.splice(0, -1) 是最后一項,是需要被掛的模塊let parent = path.splice(0, -1).reduce((memo, current) => {return memo._children[current];}, this.root);parent._children[path[path.length - 1]] = newModule
}
模塊的安裝
將所有module收集后需要對收集到數據進行整理
- state數據要合并。 通過Vue.set(parent,path[path.length-1],rootModule.state),既可以合并,又能使使 module數據成為響應式數據;
- action 和mutation 中方法訂閱(數組)
// 1.模塊收集
this._modules = new ModuleCollection(options);// 2.安裝模塊 根模塊的狀態中 要將子模塊通過模塊名 定義在根模塊上
installModule(this, state, [], this._modules.root);
this就是store, 需要完成installModule方法。installModule中傳入的有當前模塊,這個模塊可能有自己的方法。為此先改造下代碼,創建Module類。
import { forEachValue } from '../util';class Module {get namespaced() {return !!this._raw.namespaced}constructor(newModule) {this._raw = newModule;this._children = {};this.state = newModule.state}getChild(key) {return this._children[key];}addChild(key, module) {this._children[key] = module}// 給模塊繼續擴展方法}export default Module;
ModuleCollection中相應的地方稍作修改。
import Module from './module'export default class ModuleCollection {constructor(options) {// 注冊模塊 需要用到棧結構數據,[根,a],每次循環遞歸的時候將其入棧。這樣每個模塊可以清楚的知道自己的父級是誰this.register([], options)}register(path, rootModule) {// 格式化后的結果let newModule = new Module(rootModule)if (path.length === 0) { // 說明是根模塊this.root = newModule} else {// this.register(path.concat(moduleName), module); 遞歸注冊前會把module 的名放在 path的位// path.splice(0, -1) 是最后一項,是需要被掛的模塊let parent = path.splice(0, -1).reduce((memo, current) => {return memo.getChild(current);}, this.root);parent.addChild(path[path.length - 1], newModule)}// 用戶在模塊中傳了modules屬性if (rootModule.modules) {// 循環模塊 module模塊的定義 moduleName模塊的名字forEachValue(rootModule.modules, (module, moduleName) => {this.register(path.concat(moduleName), module)})}}
}
function installModule(store, rootState, path, module) {// 這里我需要遍歷當前模塊上的 actions、mutation、getters 都把他定義在store的_actions, _mutations, _wrappedGetters 中}
installModule 就需要循環對當前模塊處理對應的actions、mutation、getters。為此可以對Module類增加方法,來讓其內部自己處理。
import { forEachValue } from '../util';class Module {constructor(newModule) {this._raw = newModule;this._children = {};this.state = newModule.state}getChild(key) {return this._children[key];}addChild(key, module) {this._children[key] = module}// 給模塊繼續擴展方法forEachMutation(fn) {if (this._raw.mutations) {forEachValue(this._raw.mutations, fn)}}forEachAction(fn) {if (this._raw.actions) {forEachValue(this._raw.actions, fn);}}forEachGetter(fn) {if (this._raw.getters) {forEachValue(this._raw.getters, fn);}}forEachChild(fn) {forEachValue(this._children, fn);}
}export default Module;
function installModule(store, rootState, path, module) {// 這里我需要遍歷當前模塊上的 actions、mutation、getters 都把他定義在store的_actions, _mutations, _wrappedGetters 中// 處理mutationmodule.forEachMutation((mutation, key) => {store._mutations[key] = (store._mutations[key] || [])store._mutations[key].push((payload) => {mutation.call(store, module.state, payload)})})// 處理actionmodule.forEachAction((action, key) => {store._actions[key] = (store._actions[key] || [])store._actions[key].push((payload) => {action.call(store, store, payload)})})// 處理gettermodule.forEachGetter((getter, key) => {store._wrappedGetters[key] = function() {return getter(module.state)}})// 處理childrenmodule.forEachChild((child, key) => {// 遞歸加載installModule(store, rootState, path.concat(key), child)})}
此時,已經把每個模塊的actions、mutation、getters都掛到了store上,接下來需要對state處理。
// 將所有的子模塊的狀態安裝到父模塊的狀態上
// 需要注意的是vuex 可以動態的添加模塊
if (path.length > 0) {let parent = path.slice(0, -1).reduce((memo, current) => {return memo[current]}, rootState)// 如果這個對象本身不是響應式的 那么Vue.set 就相當于 obj[屬性 ]= 值Vue.set(parent, path[path.length - 1], module.state);
}
到此已經完成模塊的安裝,接下里是要把這些放到Vue實例上面
模塊與實例的關聯
constructor(options) {const state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._mutations = {};this._actions = {};this._wrappedGetters = {};// 1.模塊收集this._modules = new ModuleCollection(options);// 2.安裝模塊 根模塊的狀態中 要將子模塊通過模塊名 定義在根模塊上installModule(this, state, [], this._modules.root);// 3,將狀態和getters 都定義在當前的vm上resetStoreVM(this, state);}
function resetStoreVM(store, state) {const computed = {}; // 定義計算屬性store.getters = {}; // 定義store中的gettersforEachValue(store._wrappedGetters, (fn, key) => {computed[key] = () => {return fn();}Object.defineProperty(store.getters, key, {get: () => store._vm[key] // 去計算屬性中取值});})store._vm = new Vue({data: {$$state: state},computed // 計算屬性有緩存效果});
}
相對應的Store類做以下修改
export class Store {constructor(options) {const state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._mutations = {};this._actions = {};this._wrappedGetters = {};// 1.模塊收集this._modules = new ModuleCollection(options);// 2.安裝模塊 根模塊的狀態中 要將子模塊通過模塊名 定義在根模塊上installModule(this, state, [], this._modules.root);// 3,將狀態和getters 都定義在當前的vm上resetStoreVM(this, state);}commit = (type, payload) => { //保證當前this 當前store實例this._mutations[type].forEach(mutation => mutation.call(this, payload))}dispatch = (type, payload) => {this._actions[type].forEach(action => action.call(this, payload))}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
命名空間nameSpaced
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true
的方式使其成為帶命名空間的模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。
[圖片上傳失敗...(image-a03e51-1599730267452)]
平常寫上面基本上都要加上 namespaced,防止命名沖突,方法重復多次執行。現在就算每個 modules 的方法命一樣,也默認回加上這個方法別包圍的所有父結點的 key,核心就是 path 變量,在安裝模塊的時候把path處理下:
// 我要給當前訂閱的事件 增加一個命名空間
let namespace = store._modules.getNamespaced(path); // 返回前綴即可
store._modules就是模塊收集好的模塊,給它增加一個獲取命名空間的方法。
給ModuleCollection類增加一個getNamespaced方法,其參數就是path。
// 獲取命名空間, 返回一個字符串
getNamespaced(path) {let root = this.root; // 從根模塊找起來return path.reduce((str, key) => { // [a,c]root = root.getChild(key); // 不停的去找當前的模塊return str + (root.namespaced ? key + '/' : '')}, ''); // 參數就是一個字符串
}
當然Module類也需要增加一個屬性訪問器
get namespaced() {return !!this._raw.namespaced
}
接下來就是在處理mutation,action,getters的時候key的值加上namespace就可以了。
// 處理mutation
module.forEachMutation((mutation, key) => {store._mutations[namespace + key] = (store._mutations[namespace + key] || [])store._mutations[namespace + key].push((payload) => {mutation.call(store, module.state, payload)})
})// 處理action
module.forEachAction((action, key) => {store._actions[namespace + key] = (store._actions[namespace + key] || [])store._actions[namespace + key].push((payload) => {action.call(store, store, payload)})
})// 處理getter
module.forEachGetter((getter, key) => {store._wrappedGetters[namespace + key] = function() {return getter(module.state)}
})
namespaces 核心就是對數據格式的處理,來進行發布與訂閱。
插件
Vuex 的 store 接受
plugins
選項,這個選項暴露出每次 mutation 的鉤子。Vuex 插件就是一個函數,它接收 store 作為唯一參數
使用的時候:
const store = new Vuex.Store({// ...plugins: [myPlugin]
})
在插件中不允許直接修改狀態——類似于組件,只能通過提交 mutation 來觸發變化
先來看下一個vuex本地持久化的一個插件
function persists() {return function(store) { // store是當前默認傳遞的let data = localStorage.getItem('VUEX:STATE');if (data) {store.replaceState(JSON.parse(data));}store.subscribe((mutation, state) => {localStorage.setItem('VUEX:STATE', JSON.stringify(state));})}
}
插件返回一個函數,函數的參數就是store。其中replaceState, subscribe是關鍵點,也是vuex其中的2個api,接下來實現一下這2個方法。
export class Store {constructor(options) {const state = options.state; //數據變化要更新視圖 (vue的核心邏輯依賴收集)this._mutations = {};this._actions = {};this._wrappedGetters = {};// 1.模塊收集this._modules = new ModuleCollection(options);// 2.安裝模塊 根模塊的狀態中 要將子模塊通過模塊名 定義在根模塊上installModule(this, state, [], this._modules.root);// 3,將狀態和getters 都定義在當前的vm上resetStoreVM(this, state);// 插件內部會依次執行options.plugins.forEach(plugin=>plugin(this));}commit = (type, payload) => { //保證當前this 當前store實例this._mutations[type].forEach(mutation => mutation.call(this, payload))}dispatch = (type, payload) => {this._actions[type].forEach(action => action.call(this, payload))}get state() { // 屬性訪問器 new Store().state Object.defineProperty({get()})return this._vm._data.$$state}}
options.plugins.forEach(plugin=>plugin(this))就是讓所有插件依次執行,參數就是store.
this._subscribes = [];
// ...
subscribe(fn){this._subscribes.push(fn);
}
subscribe就介紹一個函數,放入到一個數組或者隊列中去。
// 處理mutation
module.forEachMutation((mutation, key) => {store._mutations[namespace + key] = (store._mutations[namespace + key] || [])store._mutations[namespace + key].push((payload) => {mutation.call(store, module.state, payload)store._subscribes.forEach(fn => {fn(mutation, rootState)})})
})
相應的在安裝模塊處理mutation的時候,需要讓訂閱的store._subscribes執行。fn的參數就是mutation和根狀態。
replaceState(state){// 替換掉最新的狀態this._vm._data.$$state = state
}
這是最簡單的改變狀態的方法,但此時雖然是ok的,但是mutation提交的還是舊值,mutation.call(store, module.state, payload)這個地方還是有點問題,module.state拿到的不是最新的狀態。
function getState(store, path) { // 獲取最新的狀態 可以保證視圖更新return path.reduce((newState, current) => {return newState[current];}, store.state);
}
可以通過這個方法能獲取到最新的轉態,相應的在處理mutation,getters的地方做相應調整。
// 處理mutation
module.forEachMutation((mutation, key) => {store._mutations[namespace + key] = (store._mutations[namespace + key] || [])store._mutations[namespace + key].push((payload) => {mutation.call(store, getState(store, path), payload)store._subscribes.forEach(fn => {fn(mutation, store.state)})})
})// 處理getter
module.forEachGetter((getter, key) => {store._wrappedGetters[namespace + key] = function() {return getter(getState(store, path))}
})
之前的mutation.state全部替換成getState去獲取最新的值。n(mutation, rootState) 也替換為fn(mutation, store.state),這樣就可以了。當然源碼中并沒有getState去或獲取最新狀態的方法。
Vuex中的輔助方法
所謂輔助函數,就是輔助我們平時使用,說白了就是讓我們偷懶。
我們在頁面組件中可能會這樣使用
<template><div id="app">我的年齡是:{{this.$store.getters.age}}<button @click="$store.commit('changeAge',5)">同步更新age</button><button @click="$store.commit('b/changeAge',10)">異步更新age</button></div>
</template>
<script>export default {computed: {},mounted() {console.log(this.$store);},
};
</script>
this.$store.getters.age這樣用當然是可以,但是就是有點啰嗦,我們可以做以下精簡
computed:{age() {return this.$store.getters.age}
}
this.$store.getters.age 直接替換成 age,效果肯定是一樣的。但是寫了在computed中寫了age方法,感覺還是啰嗦麻煩,那再來簡化一下吧,先看下用法:
computed:{...mapState(['age'])
}
mapState實現
export function mapState(stateArr) {let obj = {};for (let i = 0; i < stateArr.length; i++) {let stateName = stateArr[i];obj[stateName] = function() {return this.$store.state[stateName]}}return obj
}
那如法炮制,mapGetters
export function mapGetters(gettersArr) {let obj = {};for (let i = 0; i < gettersArr.length; i++) {let gettName = gettersArr[i];obj[gettName] = function() {return this.$store.getters[gettName]}}return obj
}
mapMutations
export function mapMutations(obj) {let res = {};Object.entries(obj).forEach(([key, value]) => {res[key] = function (...args) {this.$store.commit(value, ...args)}})return res;
}
mapActions
export function mapActions(obj) {let res = {};Object.entries(obj).forEach(([key, value]) => {res[key] = function (...args) {this.$store.dispatch(value, ...args)}})return res;
}
其中這些方法都是在一個helpers文件中。在vuex/index文件中將其導入。
import { Store, install } from './store';// 這個文件是入口文件,核心就是導出所有寫好的方法
export default {Store,install
}export * from './helpers';
createNamespacedHelpers
可以通過使用 createNamespacedHelpers
創建基于某個命名空間輔助函數。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數。
export const createNamespacedHelpers = (namespace) => ({mapState: mapState.bind(null, namespace),mapGetters: mapGetters.bind(null, namespace),mapMutations: mapMutations.bind(null, namespace),mapActions: mapActions.bind(null, namespace)
})
總結
vuex的核心功能基本是完成,也能實現基本功能,不過看源碼對很多細節做了處理,邊界做了判斷。而且其中用到 了很多設計模式以及很多技巧和算法。
通過自己實現一遍vuex,可以加深對vuex的理解和使用。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務

喜歡的朋友記得點贊、收藏、關注哦!!!