SoFunction
Updated on 2025-04-12

How to implement a simple version of vuex persistence tool

background

Recently, when using uni-app to develop mini program projects, some content that needs to be persisted cannot be called like states in other vuex, so I want to implement functions similar to vuex-persistedstate plug-in, and it seems that the code volume will not be large.

Preliminary ideas

The first way to implement is naturally the watcher mode of vue. Hijack the content that needs to be persisted, and when the content changes, the persistence method is performed.
First get a dep and observer, directly observer needs a persistent state, and pass in the callback when getting and set:

function dep(obj, key, options) {
 let data = obj[key]
 (obj, key, {
  configurable: true,
  get() {
   ()
   return data
  },
  set(val) {
   if (val === data) return
   data = val
   if(getType(data)==='object') observer(data)
   ()
  }
 })
}
function observer(obj, options) {
 if (getType(obj) !== 'object') throw ('The parameter must be object')
 (obj).forEach(key => {
  dep(obj, key, options)
  if(getType(obj[key]) === 'object') {
   observer(obj[key], options)
  }
 })
}

However, a problem was soon discovered. If a={b:{c:d:{e:1}}} is stored in storage, the operation is generally xxstorage('a',a). Next, no matter whether it is changed or not, xxstorage('a',a) needs to be re-executed, that is, no matter which descendant node of a changes, the entire object tree is re-persistent. Therefore, after monitoring the changes of the descendant node of a certain root node, you need to find the root node first, and then re-persist the corresponding items of the root node.

The next first question is how to find the parent node of the changing node.

Reconstruction of state tree

If you find the changing node down the state and confirm the change term based on the path where the node is found, the complexity is too high.

If you add a pointer to the parent node to each item in the state during the observer, can you find the corresponding root node along the pointer to the parent node when the descendant node changes?

In order to avoid the newly added pointers being traversed, it was decided to use Symbol, so the partial changes of dep are as follows:

function dep(obj, key, options) {
 let data = obj[key]
 if (getType(data)==='object') {
  data[('parent')] = obj
  data[('key')] = key
 }
 (obj, key, {
  configurable: true,
  get() {
   ...
  },
  set(val) {
   if (val === data) return
   data = val
   if(getType(data)==='object') {
    data[('parent')] = obj
    data[('key')] = key
    observer(data)
   }
   ...
  }
 })
}

Add a method to find the root node and change the corresponding storage item

function getStoragePath(obj, key) {
 let storagePath = [key]
 while (obj) {
  if (obj[('key')]) {
   key = obj[('key')]
   (key)
  }
  obj = obj[('parent')]
 }
 // storagePath[0] is the root node. storagePath records the path from the root node to the changing node. return storagePath 
}

But the problem is again. Object can achieve automatic persistence. When the array is operated with push and pop, the address of the array does not change. defineProperty cannot monitor such an address that the address has not changed (unfortunately, Proxy compatibility is too poor, and Android in the mini program does not support it directly). Of course, reassigning the array to the array every time you operate on the array can solve this problem, but it is too inconvenient to use.

Bidirectional binding when changing arrays

The solution to the array problem is to refer to the vue source code to rewrite the 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' methods of the array
When using these 7 methods to operate arrays, manually trigger the set part to update the storage content.

Add anti-shake

When vuex persists, it is easy to encounter frequent operation of state. If the storage is updated, the performance will be too poor.

Implement code

The final code is as follows:

:

/*
 Persistence related content
 */
// Rewrite Array methodconst funcArr = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
const typeArr = ['object', 'array']

function setCallBack(obj, key, options) {
 if (options && ) {
  if (getType() !== 'function') throw ('Required to be function')
  (obj, key)
 }
}

function rewriteArrFunc(arr, options) {
 if (getType(arr) !== 'array') throw ('The parameter must be array')
 (key => {
  arr[key] = function(...args) {
   this.__proto__[key].call(this, ...args)
   setCallBack(this[('parent')], this[('key')], options)
  }
 })
}

function dep(obj, key, options) {
 let data = obj[key]
 if ((getType(data))) {
  data[('parent')] = obj
  data[('key')] = key
 }
 (obj, key, {
  configurable: true,
  get() {
   if (options && ) {
    (obj, key)
   }
   return data
  },
  set(val) {
   if (val === data) return
   data = val
   let index = (getType(data))
   if (index >= 0) {
    data[('parent')] = obj
    data[('key')] = key
    if (index) {
     rewriteArrFunc(data, options)
    } else {
     observer(data, options)
    }
   }
   setCallBack(obj, key, options)
  }
 })
}

function observer(obj, options) {
 if (getType(obj) !== 'object') throw ('The parameter must be object')
 let index
 (obj).forEach(key => {
  dep(obj, key, options)
  index = (getType(obj[key]))
  if (index < 0) return
  if (index) {
   rewriteArrFunc(obj[key], options)
  } else {
   observer(obj[key], options)
  }
 })
}
function debounceStorage(state, fn, delay) {
 if(getType(fn) !== 'function') return null
 let updateItems = new Set()
 let timer = null
 return function setToStorage(obj, key) {
  let changeKey = getStoragePath(obj, key)[0]
  (changeKey)
  clearTimeout(timer)
  timer = setTimeout(() => {
   try {
    (key => {
     (this, key, state[key])
    })
    ()
   } catch (e) {
    (`middlestateContent persistence failed,The error is located at[${changeKey}]参数middle的[${key}]item`)
   }
  }, delay)
 }
}
export function getStoragePath(obj, key) {
 let storagePath = [key]
 while (obj) {
  if (obj[('key')]) {
   key = obj[('key')]
   (key)
  }
  obj = obj[('parent')]
 }
 return storagePath
}
export function persistedState({state, setItem, getItem, setDelay=0, getDelay=0}) {
 observer(state, {
  set: debounceStorage(state, setItem, setDelay),
  get: debounceStorage(state, getItem, getDelay)
 })
}
/*
 vuex automatic configuration mutation related methods
 */
export function setMutations(stateReplace, mutationsReplace) {
 (stateReplace).forEach(key => {
  let name = (/\w/, (first) => `update${()}`)
  let replaceState = (key, state, payload) => {
   state[key] = payload
  }
  mutationsReplace[name] = (state, payload) => {
   replaceState(key, state, payload)
  }
 })
}
/*
 General Method
 */
export function getType(para) {
 return (para)
  .replace(/\[object (.+?)\]/, '$1').toLowerCase()
}

Called in:

import {persistedState} from '../common/'
...
...
// Because it is a uni-app mini program, persistence is a call, and the web page is usedpersistedState({state, setItem: , setDelay: 1000})

Source code address

/goblin-pitcher/uniapp-miniprogram

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.