快捷搜索:

实现一个简易版的vuex持久化工具

背景

近来用uni-app开拓小法度榜样项目时,部分必要持久化的内容没法像其他vuex中的state那样调用,以是想着自己实现一下类似vuex-persistedstate插件的功能,貌似代码量也不会很大年夜

初步思路

首先想到的实现要领自然是vue的watcher模式。对必要持久化的内容进行挟制,当内容改变时,履行持久化的措施。

先弄个dep和observer,直接observer必要持久化的state,并传入get和set时的回调:

function dep(obj, key, options) {

let data = obj[key]

Object.defineProperty(obj, key, {

configurable: true,

get() {

options.get()

return data

},

set(val) {

if (val === data) return

data = val

if(getType(data)==='object') observer(data)

options.set()

}

})

}

function observer(obj, options) {

if (getType(obj) !== 'object') throw ('参数需为object')

Object.keys(obj).forEach(key => {

dep(obj, key, options)

if(getType(obj[key]) === 'object') {

observer(obj[key], options)

}

})

}

然而很快就发明问题,若将a={b:{c:d:{e:1}}}存入storage,操作一样平常是xxstorage('a',a),接下来无论是改了a.b照样a.b.c或是a.b.c.d.e,都必要从新履行xxstorage('a',a),也便是无论a的哪个后代节点更改了,从新持久化的都是全部object树,以是监测到某个根节点的后代节点变化后,必要先找到根节点,再将根节点对应的项从新持久化。

接下来的第一个问题便是,若何找到更改节点的父节点。

state树的从新构造

假如沿着state向下找到更改的节点,并根据找到节点的路径确认更改项,繁杂度太高。

假如在observer的时刻,对state中的每一项增加一个指向父节点的指针,在后代节点更改时,是不是就能沿着指向父节点的指针找到响应的根节点了?

为避免新增的指针被遍历到,抉择采纳Symbol,于是dep部分更改如下:

function dep(obj, key, options) {

let data = obj[key]

if (getType(data)==='object') {

data[Symbol.for('parent')] = obj

data[Symbol.for('key')] = key

}

Object.defineProperty(obj, key, {

configurable: true,

get() {

...

},

set(val) {

if (val === data) return

data = val

if(getType(data)==='object') {

data[Symbol.for('parent')] = obj

data[Symbol.for('key')] = key

observer(data)

}

...

}

})

}

再加个可以找到根节点的措施,就可以改变对应storage项了

function getStoragePath(obj, key) {

let storagePath = [key]

while (obj) {

if (obj[Symbol.for('key')]) {

key = obj[Symbol.for('key')]

storagePath.unshift(key)

}

obj = obj[Symbol.for('parent')]

}

// storagePath[0]便是根节点,storagePath记录了从根节点到更改节点的路径

return storagePath

}

然则问题又来了,object是可以实现自动持久化了,数组用push、pop这些措施操作时,数组的地址是没有更改的,defineProperty根本监测不到这种地址没变的环境(可惜Proxy兼容性太差,小法度榜样中安卓直接不支持)。当然,每次操作数组时,对数组从新赋值可以办理此问题,然则用起来太未方便了。

改变数组时的双向绑定

数组的问题,办理要领一样是参照vue源码的处置惩罚,重写数组的'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'措施

数组用这7种措施操作数组的时刻,手动触发set中部分,更新storage内容

添加防抖

vuex持久化时,轻易碰到频繁操作state的环境,假如不停更新storage,机能太差

实今世码

最后代码如下:

tool.js:

/*

持久化相关内容

*/

// 重写的Array措施

const funcArr = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

const typeArr = ['object', 'array']

function setCallBack(obj, key, options) {

if (options && options.set) {

if (getType(options.set) !== 'function') throw ('options.set需为function')

options.set(obj, key)

}

}

function rewriteArrFunc(arr, options) {

if (getType(arr) !== 'array') throw ('参数需为array')

funcArr.forEach(key => {

arr[key] = function(...args) {

this.__proto__[key].call(this, ...args)

setCallBack(this[Symbol.for('parent')], this[Symbol.for('key')], options)

}

})

}

function dep(obj, key, options) {

let data = obj[key]

if (typeArr.includes(getType(data))) {

data[Symbol.for('parent')] = obj

data[Symbol.for('key')] = key

}

Object.defineProperty(obj, key, {

configurable: true,

get() {

if (options && options.get) {

options.get(obj, key)

}

return data

},

set(val) {

if (val === data) return

data = val

let index = typeArr.indexOf(getType(data))

if (index >= 0) {

data[Symbol.for('parent')] = obj

data[Symbol.for('key')] = key

if (index) {

rewriteArrFunc(data, options)

} else {

observer(data, options)

}

}

setCallBack(obj, key, options)

}

})

}

function observer(obj, options) {

if (getType(obj) !== 'object') throw ('参数需为object')

let index

Object.keys(obj).forEach(key => {

dep(obj, key, options)

index = typeArr.indexOf(getType(obj[key]))

if (index{

try {

updateItems.forEach(key => {

fn.call(this, key, state[key])

})

updateItems.clear()

} catch (e) {

console.error(`persistent.js中state内容持久化掉败,差错位于[${changeKey}]参数中的[${key}]项`)

}

}, delay)

}

}

export function getStoragePath(obj, key) {

let storagePath = [key]

while (obj) {

if (obj[Symbol.for('key')]) {

key = obj[Symbol.for('key')]

storagePath.unshift(key)

}

obj = obj[Symbol.for('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自动设置设置设备摆设摆设mutation相关措施

*/

export function setMutations(stateWordStr, mutationsWordStr) {

Object.keys(stateWordStr).forEach(key => {

let name = key.replace(/\w/, (first) => `update${first.toUpperCase()}`)

let replaceState = (key, state, payload) => {

state[key] = payload

}

mutationsWordStr[name] = (state, payload) => {

replaceState(key, state, payload)

}

})

}

/*

通用措施

*/

export function getType(para) {

return Object.prototype.toString.call(para)

.replace(/\[object (.+?)\]/, '$1').toLowerCase()

}

persistent.js中调用:

import {persistedState} from '../common/tools.js'

...

...

// 由于是uni-app小法度榜样,持久化是调用uni.setStorageSync,网页就用localStorage.setItem

persistedState({state, setItem: uni.setStorageSync, setDelay: 1000})

源码地址

https://github.com/goblin-pitcher/uniapp-miniprogram

您可能还会对下面的文章感兴趣: