> 本文首發於[掘金](https://juejin.cn/post/7264128388288708664),未經許可禁止轉載 Vuex4 是 Vue 的狀態管理工具,Vuex 和單純的全局對象有以下兩點不同: 1. Vuex 的狀態存儲是響應式的 2. 不能直接改變 store 中的狀態。改變 ...
本文首發於掘金,未經許可禁止轉載
Vuex4 是 Vue 的狀態管理工具,Vuex 和單純的全局對象有以下兩點不同:
- Vuex 的狀態存儲是響應式的
- 不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地 提交 (commit) mutation。
本文手寫部分分為八個部分,基本包含了 Vuex 的功能。
- 實現獲取state並響應式修改state
- 實現getters
- 實現 commit 和 dispatch
- 註冊模塊
- 註冊模塊上的 getters,mutations,actions 到 store 上
- 命名空間
- 嚴格模式
- 插件模式
準備工作
創建名字叫 vuex_source
的工程
vue-cli3 create vuex_source
上面命令和使用 vue create vuex_source
創建項目是等價的,我電腦安裝了 vue-cli2
和 vue-cli3
,在 vue-cli3
裡面修改了 cmd
文件,所以可以用上面命令。
選擇 Vuex,使用空格選擇或取消選擇
啟動項目如果如下圖報錯
可以試試輸入命令
$env:NODE_OPTIONS="--openssl-legacy-provider"
基本使用
使用 createStore 創建一個 store
import { createStore } from 'vuex'
export default createStore({
strict:true,
state: {
count:1
},
getters: {
double(state){
return state.count * 2
}
},
mutations: {
mutationsAdd(state,preload){
state.count += preload
}
},
actions:{
actionAdd({commit},preload){
setTimeout(() => {
commit('mutationsAdd',preload)
}, 1000);
}
}
})
main.js 中引入
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
在 app.vue 中使用 store
<template>
數量:{{count}} {{$store.state.count}}
<br>
double:{{double}} {{$store.getters.double}}
<br>
<!-- 嚴格模式下會報錯 -->
<button @click="$store.state.count++">錯誤增加</button>
<br>
<button @click="mutationsAdd">正確增加mutation</button>
<br>
<button @click="actionAdd">正確增加 action</button>
</template>
<script>
import { computed } from "vue";
import { useStore } from "vuex";
export default {
name: 'App',
setup(){
const store = useStore()
const mutationsAdd = () =>{
store.commit('mutationsAdd',1)
}
const actionAdd = () =>{
store.dispatch('actionAdd',1)
}
return {
// 來自官網解釋:從 Vue 3.0 開始,getter 的結果不再像計算屬性一樣會被緩存起來。這是一個已知的問題,將會在 3.1 版本中修複。
// 使用 count:store.state.count 返回的話,模板中的 {{count}}並不是響應式的,這裡必須加上 computed 此時響應式的
count:computed(() => store.state.count),
double:computed(() => store.getters.double),
mutationsAdd,
actionAdd,
}
}
}
</script>
編寫源碼
實現獲取state並響應式修改state
修改 App.vue 的引用,@/vuex 是需要編寫的源碼的文件夾
import { useStore } from "@/vuex"; // 之前是import { useStore } from "vuex";
修改 store 的引用
import { createStore } from '@/vuex'
在 src 目錄下創建 vuex文件夾,裡面添加 index.js
在 index.js 中添加 createStore 和 useStore 函數,createStore 用來創建 store,useStore 供頁面調用
// vuex/index.js
class Store{
constructor(options){
}
}
// 創建 store,多例模式
export function createStore(options){
return new Store(options)
}
// 使用 store
export function useStore(){}
createStore 創建出的store,在main.js 中 調用 use 方法
createApp(App).use(store)
use 會調用 store 的 install 方法,將 store 安裝到 Vue 上,所以 Store 類中還需要添加 install 方法
const storeKey = 'store' // 預設一個 store 名
class Store{
constructor(options){
}
install(app,name){
// app 是vue3暴露的對象
// 在根組件上綁定了 store,子組件要用到 store
// 根組件就需要 provide 出去,子組件 inject 接收
app.provide(name || storeKey,this)
}
}
// 創建 store
export function createStore(options){
return new Store(options)
}
// 使用 store
export function useStore(name){
// inject 去找父組件的 provide 的東西
return inject(name!==undefined?name:storeKey)
}
此時在 App.vue 中列印的就是一個空對象
// App.vue
const store = useStore()
console.log(store);
在 Store 類中綁定傳進來的 state
constructor(options){
this.state = options.state
}
列印就是
App.vue 中添加如下模板
<template>
數量:{{count}} // 正常列印 1
數量:{{$store.state.count}} // 報錯了
<br>
<button @click="$store.state.count++">錯誤增加</button>
</template>
<script>
import { computed } from "vue";
import { useStore } from "@/vuex";
export default {
name: 'App',
setup(){
const store = useStore()
console.log(store);
return {
count:computed(() => store.state.count)
}
}
}
</script>
上面模板中的 {{$store.state.count}}
會報錯,是因為 $store 沒有綁定到 this 上。vue3 中綁定到 this 可以用 app.config.globalProperties[屬性名]
// createApp(App).use(store,name)會調用store的install方法
install(app,name){
// app 是vue3暴露的對象
app.provide(name || storeKey,this)
app.config.globalProperties.$store = this
}
}
$store 綁定到 this 後就不會報錯了
但此時點擊 錯誤增加
的按鈕沒有任何效果,
因為此時 store.state 並不是響應式的,需要增加響應式效果,vue3 為複雜數據提供了 reactive
class Store{
constructor(options){
// 這裡給options.state加了一層,用 data 包裹是為了重新賦值的時候可以直接 this._store.data = 。。。 ,而不用再使用 reactive
this._store = reactive({data:options.state})
this.state = this._store.data
}
// createApp(App).use(store,name)會調用store的install方法
install(app,name){
// app 是vue3暴露的對象
app.provide(name || storeKey,this)
app.config.globalProperties.$store = this
}
}
這時候 錯誤增加
的按鈕就有效果了
實現getters
模板中是使用 getters 是以屬性的方式:
// App.vue
數量:{{count}}
數量:{{$store.state.count}}
<br>
double:{{double}}
double:{{$store.getters.double}}
<br>
<button @click="$store.state.count++">錯誤增加</button>
在 store.js 中定義的getters 是由一個大對象裡面包含多個函數組成
getters: {
double(state){
return state.count * 2
}
},
在 store 中 double 是函數,返回的 state.count * 2
的結果。 在模板中使用的是 $store.getters.double ,這個 double 是 getters 上的一個屬性。所以這裡需要進行轉換
const forEachValue = function(obj,fn){
return Object.keys(obj).forEach((key) =>{
fn(obj[key],key)
})
}
class Store{
constructor(options){
this._store = reactive({data:options.state})
this.state = this._store.data
this.getters = Object.create(null)
forEachValue(options.getters,(fn,key) => {
// 當模板解析 $store.getters.double 時,
// 就去執行 options.getters裡面對應屬性的函數,並將函數結果賦予該屬性
Object.defineProperty(this.getters,key,{
// vue3.2之前的vuex中不能使用計算屬性 computed,導致每次訪問的時候都會執行函數引發潛在性能問題
// vue3.2修複了這個bug
get:() => {
return fn(this.state)
}
})
})
}
// createApp(App).use(store,name)會調用store的install方法
install(app,name){
// app 是vue3暴露的對象
app.provide(name || storeKey,this)
app.config.globalProperties.$store = this
}
}
forEachValue 函數接收一個對象參數 obj 和一個處理函數參數 fn;裡面會遍歷對象,迴圈調用 fn;
這裡遍歷 options.getters ,響應式註冊到 this.getters 上,這樣當模板解析 $store.getters.double 時,就會執行對應的 fn
點擊錯誤增加
按鈕,改變 $store.state.count 的值進而導致 getters 值的變化
實現 commit 和 dispatch
commit 和 dispatch 在組件中是這樣使用的:
<template>
<button @click="mutationsAdd">正確增加 mutation</button>
<br>
<button @click="actionAdd">正確增加 action</button>
</template>
<script>
import { useStore } from "@/vuex";
export default {
name: 'App',
setup(){
const store = useStore()
const mutationsAdd = () =>{
store.commit('mutationsAdd',1)
}
const actionAdd = () =>{
store.dispatch('actionAdd',1)
}
return {
mutationsAdd,
actionAdd,
}
}
}
</script>
store.js 中定義的是這樣的:
mutations: {
mutationsAdd(state,preload){
state.count += preload
}
},
actions:{
// 非同步調用
actionAdd({commit},preload){
setTimeout(() => {
commit('mutationsAdd',preload)
}, 1000);
}
}
調用 mutation : store.commit(mutation類型,參數)
調用 action : store.dispatch(action類型,參數)
在 Store 類中實現 commit:
class Store{
constructor(options){
// 將 store.js 中定義的 mutations 傳進來
this._mutations = options.mutations
this.commit = function(name,preload){
if(this._mutations[name]!==undefined){
// 根據傳進來的類型,調用對應的方法
this._mutations[name](this.state,preload)
}
}
}
}
效果如下,數量每次增加 1
在 Store 類中實現 dispatch:
class Store{
constructor(options){
// 將 store.js 中定義的 actions 傳進來
this._actions = options.actions
this.dispatch = function(name,preload){
if(this._actions[name]!==undefined){
// 根據傳進來的類型,調用對應的方法
let fn = this
// dispatch 進來調用的是 actionAdd({commit},preload)
this._actions[name].apply(fn,[fn].concat(preload))
}
}
}
}
dispatch 調用的參數是({commit},preload),所以這裡傳進去需要是 (this,preload)
看看效果:
這裡報了錯,由 dispatch 觸發 actions 正常,但 actions 觸發 對應的 mutations 出錯了,顯示 this 是 undefined。那麼這裡就要修改下之前的 commit 實現了,先用一個變數將 Store 類實例的 this 保存起來
class Store{
constructor(options){
// 這裡創建一個 store 變數保存 this 是方便之後嵌套函數裡面訪問當前 this
let store = this
this._mutations = options.mutations
this.commit = function(name,preload){
if(store._mutations[name]!==undefined){
store._mutations[name](store.state,preload)
}
}
this._actions = options.actions
this.dispatch = function(name,preload){
if(store._actions[name]!==undefined){
store._actions[name].apply(store,[store].concat(preload))
}
}
}
}
這樣就可以了
註冊模塊
平常使用中定義 modules 如下
// store/index.js
import { createStore } from 'vuex'
export default createStore({
// strict: true,
state: {
count: 1
},
// ...
modules: {
aCount: {
state: {
count: 1
},
modules: {
cCount: {
state: {
count: 1
},
},
}
},
bCount: {
state: {
count: 1
},
}
}
})
組件中使用
// App.vue
<template>
數量(根模塊):{{$store.state.count}} <button @click="$store.state.count++">增加</button>
<br>
數量(aCount模塊):{{$store.state.aCount.count}} <button @click="$store.state.aCount.count++">增加</button>
<br>
數量(cCount模塊):{{$store.state.aCount.cCount.count}} <button @click="$store.state.aCount.cCount.count++">增加</button>
<br>
</template>
先實現將用戶定義的多個 modules 進行格式化,創造父子關係
用戶傳進來的數據
格式化後的數據
上面的每個 modules 下的數據格式如下
this._raw = modules // 保存不做處理的源數據
this.state = modules.state // 保存狀態
this.children = {} // 創建子對象
- _raw 保存不做處理的源數據
- state 保存狀態
- children 保存子模塊
比如 bCount
// 格式化前
bCount:{
state: {
count: 1
},
}
// 格式化後
bCount: {
_raw: bModule, // 這裡放的是格式化前的數據
state: bModule.state,
children: {}
},
所以這裡定義一個類 moduleCollection,專門用來收集模塊,將用戶寫的嵌套 modules 格式化,創造父子關係
class Store {
constructor(options) {
const store = this
// 收集模塊,將用戶寫的嵌套modules格式化,創造父子關係
store._modules = new moduleCollection(options)
console.log(store._modules)
}
}
在 moduleCollection類中定義 register 方法處理數據,定義 this.root 保存處理過後的數據
class moduleCollection{
constructor(rootModule){
// root 存儲格式化的數據,方便後續安裝到 store.state 上
this.root = null
this.register(rootModule,[])
}
register(rootModule,path){
}
}
register 方法接受兩個參數
一個表示當前處理的模塊數據 rootModule ,一個表示當前處理的是誰的模塊數據 path
path之所以用數組表示,是因為後面建造父子關係時,使用path可以進行關聯
比如 path 是空數組,則表示處理的是根模塊的數據,是 [a] 則表示處理的是 a 模塊,是[a,c] 則表示處理的是 c 模塊的數據,並且,c模塊的數據要加到 a 模塊的 children 中。
對於 register 方法:
首先將用戶定義的 store 格式化賦值給 this.root,這裡可以抽象出一個類,因為每個模塊的格式都是 _raw,state,children
class Module{
constructor(modules){
this._raw = modules
this.state = modules.state
this.children = {}
}
getChild(key){
return this.children[key]
}
addChild(key,module){
this.children[key] = module
}
}
class moduleCollection{
register(rootModule,path){
const newModule = new Module(rootModule)
if(path.length===0){
this.root = newModule
}
}
}
然後判斷 最外層的 store 中也就是根模塊還有沒有子模塊,如果有,繼續遞歸格式化子模塊數據
// 如果根模塊下還有子模塊,則繼續遞歸註冊
if(rootModule.modules){
Object.keys(rootModule.modules).forEach((key) =>{
this.register(rootModule.modules[key],path.concat(key))
})
}
用戶定義的 store 中,根模塊下定義了子模塊,子模塊裡面分別是 aCount 和 bCount,所以執行到上面代碼時, key 就是 aCount,bCount
rootModule.modules[key] 是他們對應的模塊數據
當執行到 aCount 模塊時,此時的 path 是[a],代表處理 aCount 的數據,這時我們要在根模塊上添加 aCount,如果 path 是 [aCount,cCount],則需要在 aCount 模塊上添加 cCount 模塊,所以這裡需要定義一個尋找父模塊的方法。
使用 path.slice(0,-1) 得到父模塊的 key,預設是根模塊
const parent = path.slice(0,-1).reduce((modules,current) =>{
return modules.getChild(current)
},this.root)
參數 modules 代表上一次執行結果,current代表當前元素,初始傳入根模塊
這裡如果 path 是 [a], 傳入給 reduce 時是 [] ,那麼返回的就是 this.root
path 是[a,c],傳入給reduce 時是 [a], 當執行module.getChild(current) 實際上就是 this.root.getChild(a)
找到父模塊後,給父模塊的 children 添加 modules
parent.addChild(path[path.length-1],newModule)
moduleCollection 類完整代碼:
class moduleCollection{
constructor(rootModule){
// root 存儲格式化的數據,方便後續安裝到 store.state 上
this.root = null
this.register(rootModule,[])
}
register(rootModule,path){
// 註冊模塊,每個模塊的格式都是
// _raw: rootModule,
// state: rootModule.state,
// children: {}
// 所以給傳進來的模塊都格式化一下
const newModule = new Module(rootModule)
// 註冊根模塊
if(path.length===0){
this.root = newModule
}else{
// 註冊子模塊,將子模塊添加到對應的父模塊,通過 path路徑可以知道對應的父模塊
const parent = path.slice(0,-1).reduce((modules,current) =>{
return modules.getChild(current)
},this.root)
parent.addChild(path[path.length-1],newModule)
}
// 如果根模塊下還有子模塊,則繼續遞歸註冊
if(rootModule.modules){
Object.keys(rootModule.modules).forEach((key) =>{
this.register(rootModule.modules[key],path.concat(key))
})
}
}
}
得到格式化數據後,需要將各個模塊的 state 安裝在 store.state 上,以便之後調用:$store.state.aCount.cCount.count
,$store.state
安裝後的樣子應該是:
state:{
count:1,
aCount:{
count:1,
cCount:{
count:1
}
},
bCount:{
count:1
}
}
創建一個 installModules 函數
function installModules(store,modules,path){}
class Store {
constructor(options) {
const store = this
store._modules = new moduleCollection(options)
console.log(store._modules)
installModules(store,store._modules.root,[])
console.log(store.state)
}
}
store 是當前 Store 類的實例對象,模塊安裝的地方
modules 是要安裝的模塊
path 對應父子關係
installModules 方法和 register 方法類似
function installModules(store,modules,path){
if(path.length===0){
store.state = modules.state
}else{
const parent = path.slice(0,-1).reduce((result,current) =>{
return result[current]
},store.state)
parent[path[path.length-1]] = modules.state
}
if(modules.children){
Object.keys(modules.children).forEach((key) =>{
installModules(store,modules.children[key],path.concat(key))
})
}
}
這樣也就得到了一個完整的 state
在組件中引用也能正確顯示了
註冊模塊上的 getters,mutations,actions 到 store 上
class Store {
constructor(options) {
const store = this
// 收集模塊,將用戶寫的嵌套modules格式化,創造父子關係
store._modules = new moduleCollection(options)
// 定義私有變數存放對應的 getters,actions,mutations
store._getters = Object.create(null)
store._mutations = Object.create(null)
store._actions = Object.create(null)
installModules(store,store._modules.root,[])
}
}
同樣也是在 installModules 方法裡面進行存放操作
在格式化模塊時,已經將每個模塊定義成這樣的數據格式:
this._raw = modules
this.state = modules.state
this.children = {}
_raw 存放的就是源數據,沒有被格式化的數據。
所以,取得模塊上的 getters 就是 modules._raw.getters;
取得模塊上的 mutations 就是 modules._raw.mutations;
取得模塊上的 actions 就是 modules._raw.actions;
遍歷 modules._raw.getters ,安裝到 store._getters 上。這裡需要註意的是
getters的參數是 state,這個 state 本來是 modules._raw.state,但 _raw.state沒有響應式,而後面store.state 是響應式的,需要根據 path 取得store.state裡面對應的 state
function getCurrentState(state,path){
return path.reduce((result,current) =>{
return result[current]
},state)
}
function installModules(store,modules,path){
···
···
if(modules._raw.getters){
forEachValue(modules._raw.getters,(getters,key) =>{
store._getters[key] = () =>{
// 這裡的參數不能是 modules._raw.state,沒有響應式
// 而後面 store.state 會是響應式的,需要根據 path 取得store.state裡面對應的 state
return getters(getCurrentState(store.state,path))
}
})
}
···
···
}
註冊 mutations
if(modules._raw.mutations){
forEachValue(modules._raw.mutations,(mutations,key) =>{
if(!store._mutations[key]){
store._mutations[key] = []
}
store._mutations[key].push((preload) =>{ // store.commit(key,preload)
mutations.call(store,getCurrentState(store.state,path),preload)
})
})
}
在模塊裡面,可能有多個同名的 mutations,所以這裡可能有多個同名 key,需要用數組包裝起來
註冊 actions
if(modules._raw.actions){
forEachValue(modules._raw.actions,(actions,key) =>{
if(!store._actions[key]){
store._actions[key] = []
}
store._actions[key].push((preload) =>{
// store.dispatch({commit},preload)
// actions執行後返回的是promise
let res = actions.call(store,store,preload)
if(!isPromise(res)){
return Promise.resolve(res)
}
return res
})
})
}
和 mutations 一樣,也可能會有多個重名的 actions。區別是 actions 執行完後返回的是一個 promise
命名空間
命名空間的用法,添加 namespaced:true
// store.js
aCount: {
namespaced:true,
state: {
count: 1
},
mutations: {
mutationsAdd(state, preload) {
state.count += preload
}
},
modules: {
cCount: {
namespaced:true,
state: {
count: 1
},
mutations: {
mutationsAdd(state, preload) {
state.count += preload
}
},
},
}
}
頁面中就可以使用 $store.commit('aCount/mutationsAdd',1)
調用 aCount 下的 mutationsAdd
$store.commit('aCount/cCount/mutationsAdd',1)
調用 cCount 下的 mutationsAdd
在安裝模塊的時候,通過檢測模塊是否定義 namespaced 為 true,來給安裝的模塊的 actions,mutations 添加命名空間首碼
function getNameSpace(modules,path){
let root = modules.root
// 傳入的是 根模塊
// 當 path 是[],返回空字元串
// 2、當 path 是 [aCount] ,根據path,取得根模塊下的對應的 aCount , modules.getChild(aCount)
// 然後判斷 aCount 模塊下是否定義namespaced,有則返回 aCount/
// 當 path 是 [aCount,cCount] ,重覆2,然後根據步驟2 取得的子模塊,再往下找子模塊cCount,
// 然後判斷 cCount 模塊下是否定義namespaced,有則返回 aCount/cCount/
// [] => '' [aCount] => 'aCount/' [aCount,cCount] => 'aCount/cCount'
return path.reduce((module,current) =>{
root = root.children[current]
return root.namespaced?(module+current+'/'):''
},'')
}
function installModules(store,modules,path,root){
···
// 所以這裡先根據path取得設置了 namespaced 的模塊名字,拼接後註冊到 mutations,actions的名字上
const namespace = getNameSpace(root,path)
console.log(namespace)
if(modules._raw.mutations){
forEachValue(modules._raw.mutations,(mutations,key) =>{
if(!store._mutations[namespace + key]){
store._mutations[namespace + key] = []
}
store._mutations[namespace + key].push((preload) =>{ // store.commit(key,preload)
mutations.call(store,getCurrentState(store.state,path),preload)
})
})
}
}
在 getNameSpace 方法中,傳入的參數 path 表示的是有父子關係的模塊名組成的數組,通過 path 找到對應的模塊,判斷是否定義 namespaced 為 true。最終返回命名空間字元串
嚴格模式
要設置嚴格模式,在根節點上指定 strict:true 即可。
const store = createStore({
// ...
strict: true
})
設置了嚴格模式,沒有通過 mutations 改變狀態都會彈出一個報錯信息(即通過 $store.state.count++ 直接改變狀態)
並且官方建議不要在發佈環境下啟用嚴格模式
那麼這裡可以創建一個變數 isCommiting ,用來判斷是否通過 mutations 改變狀態,只要是執行了 mutations 方法的都去改變 isCommiting。
store.commit = (type,preload) =>{
this.withCommit(() =>{
if(store._mutations[type]){
store._mutations[type].forEach((fn) =>{
fn(preload)
})
}
})
}
withCommit(fn){
this.isCommiting = true
fn()
this.isCommiting = false
}
上面執行 mutations 之前,isCommiting 為 true,此時只需要知道,當狀態變化的時候 isCommiting 不為 true,則提示報錯。
這裡每個數據狀態變化都需要知道 isCommiting 的值,所以需要深度監聽整個狀態。深度監聽會帶來一定性能損耗,所以嚴格模式不建議在生產環境使用。
if(store.strict){
watch(() =>store._store.data,() =>{
console.assert(store.isCommiting,'do not mutate vuex store state outside mutation handlers.')
},{deep:true,flush:'sync'})
}
效果如下:
數量(根模塊):{{$store.state.count}}
<button @click="$store.commit('mutationsAdd',1)">增加</button>
<button @click="$store.state.count++">錯誤增加</button>
插件模式
Vuex 的插件實際上是一個函數,store 作為這個函數的唯一參數。定義插件即在 createStore 中定義 plugins 選項,選項是數組格式,可包含多個插件函數。這些插件函數會在創建 store 時依次執行
const plugins1 = (store) => {
// 當 store 初始化後調用
store.subscribe((mutation, state) => {
// 每次 mutation 之後調用
// mutation 的格式為 { type, payload }
})
}
const store = createStore({
// ...
strict: true,
plugins:[plugins1],
})
在插件中可以調用 subscribe 方法,參數是當前調用的 mutation 和調用 mutation 後的 state ,此時的 state 是最新的。並且 subscribe 中的函數都是在每次 mutation 之後調用。
根據這些,來實現 subscribe 方法
class Store {
constructor(options) {
const store = this
// ...
store._subscribe = []
store.subscribe = (fn) =>{
store._subscribe.push(fn)
}
const plugins = options.plugins
plugins.forEach(fn => {
fn(store)
});
}
定義 _subscribe 私有變數數組,用來存儲插件中 subscribe 的函數。如果定義了 plugins 選項,那麼依次執行選項中的插件函數。
store.commit = (type,preload) =>{
this.withCommit(() =>{
if(store._mutations[type]){
store._mutations[type].forEach((fn) =>{
fn(preload)
})
store._subscribe.forEach(fn => {
fn({type:type,preload:preload},store.state)
});
}
})
}
在調用完 mutation 後,迴圈調用 _subscribe 的函數。這樣每個函數中的 state 參數都是最新的
現在來實現一個持久化存儲的插件,將狀態存儲在 sessionStorage 中,頁面刷新後,從 sessionStorage 中取出並替換為最新狀態。
const customPlugin = (store) =>{
const local = sessionStorage.getItem('vuexState')
if(local){
store.replaceState(JSON.parse(local))
}
store.subscribe((mutation,state) =>{
sessionStorage.setItem('vuexState',JSON.stringify(state))
})
}
在 store.subscribe 參數函數中,每次調用 mutation 後,將狀態存儲在 sessionStorage 中。store.replaceState 則是一個替換狀態的方法。
replaceState(newState){
this.withCommit(() =>{
this._store.data = newState
})
}
效果如下:
總結
Vuex 在項目中用了很久,只知其然不知其所以然,故研究學習並實現出來。
從理解思路到手寫出來,然後將實現過程記錄下來就有了這篇文章,這個過程斷斷續續持續了大概一個月,項目和文章基本都是利用下班時間寫的,確實挺累的,不過實現出來後往回看,還是學到很多東西,還挺欣慰的;文章有不足的地方還請各位大佬指正;