DIY A Vuex Persistence Plug-in

  javascript, vue.js, vuex

When doing Vue related projects, there will always be a situation where the Store content is lost due to page refresh. Complex projects often involve a large number of states that need to be managed. If all of them need to be retrieved just because of one refresh, the cost is too high.

So can we make these states permanent locally? The answer is yes, the community also provides many solutions, such asvuex-persistedstate,vuex-localstorageSuch as plug-ins, these plug-ins provide relatively perfect functions. Of course, besides directly using third-party plug-ins, it is also very easy for us to DIY one ourselves.

This persistent plug-in has two main functions:

  1. Data that needs to be persisted can be selected.
  2. Persistent data can be read locally and updated to Store.

Next, we will complete a Vuex persistence plug-in from the above two function points.

Gist address:https://gist.github.com/jrain …
Online experience address:https://codepen.io/jrainlau/p …

First, learn to write a Vuex plug-in

QuoteVuex. comExamples of:

Vuex’s store accepts the plugins option, which exposes the hook for each mutation. Vuex plug-in is a function that receives store as the only parameter:

const myPlugin = store => {
 //Called after store initialization
 store.subscribe((mutation, state) => {
 //Called after each mutation
 // mutation is in the format {type, payload}
 })
 }

Then use it like this:

const store = new Vuex.Store({
 //     ...
 plugins: [myPlugin]
 })

Everything is so simple, the key point is to pass inside the plug-in.store.subscribe()To monitor mutation. In our persistence plug-in, it is within this function that data is persisted.

Second, allow users to select data that need to be persisted.

It is preferred to initialize the body function of a plug-in:

const VuexLastingPlugin = function ({
 watch: '*',
 storageKey: 'VuexLastingData'
 }) {
 return store => {}
 }

Of the plug-inswatchThe default is select all symbols*, allowing an to be passed inarrayThe contents of the array are the key values of the data to be persisted, such as['key1', 'key2']Wait. Then you can gostore.subscribe()There is a persistent operation on the data.

const VuexLastingPlugin = function ({
 watch: '*'
 }) {
 return store => {
 store.subscribe((mutation, state) => {
 let watchedDatas = {}
 //If all is selected, the entire state is persisted
 //Otherwise, only the listed state will be persisted
 if (watch === '*') {
 watchedDatas = state
 } else {
 watch.forEach(key => {
 watchedDatas[key] = state[key]
 })
 }
 //persist via localStorage
 localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas))
 })
 }
 }

According to Vuex’s specification, state can be modified only by mutation, so according to the above steps, we completed the real-time persistence of data.

There is also a small problem here, namely writingwatchThe array element of the parameter must beThe outermost key in the state, does not support the shape such asa.b.cSuch nested keys. This function is obviously not perfect, so we hope to increase support for nested key.

Create a new tool functiongetObjDeepValue()

function getObjDeepValue (obj, keysArr) {
 let val = obj
 keysArr.forEach(key => {
 val = val[key]
 })
 return val
 }

This function receives aObjectAnd oneKey value array, return the corresponding value, let’s verify:

var obj = {
 a: {
 name: 'aaa',
 b: {
 name: 'bbb',
 c: {
 name: 'ccc'
 }
 }
 }
 }
 
 getObjDeepValue(obj, 'a.b.c'.split('.'))
 
 // => { name: "ccc" }

After the verification is successful, you can put this tool function into thestore.subscribe()In the use of:

store.subscribe((mutation, state) => {
 let watchedDatas = {}
 if (watch === '*') {
 watchedDatas = state
 } else {
 watch.forEach(key => {
 //keys like a.b.c. will be saved as deep_a.b.c
 if (data.split('.').length > 1) {
 watchedDatas[`deep_${key}`] = getObjDeepValue(state, key.split('.'))
 } else {
 watchedDatas[key] = state[key]
 }
 })
 }
 
 localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas))
 })

After this transformation, throughwatchThe key value written will support nested form, and the whole plug-in will be more flexible.

3. Read persistent data from local and update to Store

From the above steps, we have been able to flexibly monitor the data in the store and persist them. The next task is to complete how to read the local persistent data after the browser refreshes and update them to the store.

Add a default oftrueOptions forautoInitAs a switch to automatically read and update store. Functionally, after refreshing the browser, the plug-in should automatically read the data stored in localStorage and update them to the current store. The key point is how todeep_${key}The value of is correctly assigned to the corresponding place, so we need to create another tool functionsetObjDeepValue()

function setObjDeepValue (obj, keysArr, value) {
 let key = keysArr.shift()
 if (keysArr.length) {
 setObjDeepValue(obj[key], keysArr, value)
 } else {
 obj[key] = value
 }
 }

This function receives aObjectOneKey value array, and onevalue, set the value of key corresponding to the object, let’s verify:

var obj = {
 a: {
 name: 'aaa',
 b: {
 name: 'bbb',
 c: {
 name: 'ccc'
 }
 }
 }
 }
 
 setObjDeepValue(obj, ['a', 'b', 'c'], 12345)
 
 /**
 obj = {
 a: {
 name: 'aaa',
 b: {
 name: 'bbb',
 c: 12345
 }
 }
 }
 */

With this tool and method, you can officially operate the store.

if (autoInit) {
 const localState = JSON.parse(storage && storage.getItem(storageKey))
 const storeState = store.state
 if (localState) {
 Object.keys(localState).forEach(key => {
 //values in the form of deep_a.b.c are assigned to state.a.b.c
 if (key.includes('deep_')) {
 let keysArr = key.replace('deep_', '').split('.')
 setObjDeepValue(storeState, keysArr, localState[key])
 delete localState[key]
 }
 })
 //modify store.state through Vuex's built-in store.replaceState method
 store.replaceState({ ...storeState, ...localState })
 }
 }

The above code will read the value of storage when the page is initialized, and then take the form ofdeep_a.b.cThe value of is extracted and assigned tostore.state.a.b.cAmong them, the final passstore.replaceState()The method updates the value of the entire store.state This completes the function of reading persistent data locally and updating to Store.

IV. Case Testing

We can write a case to test the operation of this plug-in.

Online experience:https://codepen.io/jrainlau/p …

图片描述

App.vue

<template>
 <div id="app">
 <pre>{{$store.state}}</pre>
 
 <button @click="updateA">updateA</button>
 <button @click="updateX">UpdateX</button>
 </div>
 </template>
 
 <script>
 export default {
 name: 'app',
 methods: {
 updateA () {
 let random = Math.random()
 this.$store.commit('updateA', {
 name: 'aaa' + random,
 b: {
 name: 'bbb' + random,
 c: {
 name: 'ccc' + random
 }
 }
 })
 },
 updateX () {
 this.$store.commit('updateX', { name: Math.random() })
 }
 }
 }
 </script>

store.js

import Vue from 'vue'
 import Vuex from 'vuex'
 import VuexPlugin from './vuexPlugin'
 
 Vue.use(Vuex)
 
 export default new Vuex.Store({
 plugins: [VuexPlugin({
 watch: ['a.b.c', 'x']
 })],
 state: {
 a: {
 name: 'aaa',
 b: {
 name: 'bbb',
 c: {
 name: 'ccc'
 }
 }
 },
 x: {
 name: 'xxx'
 }
 },
 mutations: {
 updateA (state, val) {
 state.a = val
 },
 updateX (state, val) {
 state.x = val
 }
 }
 })

As can be seen from the case, we have persisted the data for state.a.b.c and state.x When the whole state.a is modified, only state.a.b.c is stored in localStorage, and only this attribute is modified when data is restored. State.x is monitored in its entirety, so any changes to state.x will be persisted and can be restored.

The end

This Vuex plug-in only works in the browser environment, without considering SSR. Students who need it can expand on this basis and will no longer discuss it. If you find any mistakes or imperfections in the article, please leave a message and discuss with me.