如何實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的vuex持久化工具
背景
最近用uni-app開(kāi)發(fā)小程序項(xiàng)目時(shí),部分需要持久化的內(nèi)容沒(méi)法像其他vuex中的state那樣調(diào)用,所以想著自己實(shí)現(xiàn)一下類似vuex-persistedstate插件的功能,貌似代碼量也不會(huì)很大
初步思路
首先想到的實(shí)現(xiàn)方式自然是vue的watcher模式。對(duì)需要持久化的內(nèi)容進(jìn)行劫持,當(dāng)內(nèi)容改變時(shí),執(zhí)行持久化的方法。
先弄個(gè)dep和observer,直接observer需要持久化的state,并傳入get和set時(shí)的回調(diào):
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 ('參數(shù)需為object')
Object.keys(obj).forEach(key => {
dep(obj, key, options)
if(getType(obj[key]) === 'object') {
observer(obj[key], options)
}
})
}
然而很快就發(fā)現(xiàn)問(wèn)題,若將a={b:{c:d:{e:1}}}存入storage,操作一般是xxstorage('a',a),接下來(lái)無(wú)論是改了a.b還是a.b.c或是a.b.c.d.e,都需要重新執(zhí)行xxstorage('a',a),也就是無(wú)論a的哪個(gè)后代節(jié)點(diǎn)變動(dòng)了,重新持久化的都是整個(gè)object樹(shù),所以監(jiān)測(cè)到某個(gè)根節(jié)點(diǎn)的后代節(jié)點(diǎn)變更后,需要先找到根節(jié)點(diǎn),再將根節(jié)點(diǎn)對(duì)應(yīng)的項(xiàng)重新持久化。
接下來(lái)的第一個(gè)問(wèn)題就是,如何找到變動(dòng)節(jié)點(diǎn)的父節(jié)點(diǎn)。
state樹(shù)的重新構(gòu)造
如果沿著state向下找到變動(dòng)的節(jié)點(diǎn),并根據(jù)找到節(jié)點(diǎn)的路徑確認(rèn)變動(dòng)項(xiàng),復(fù)雜度太高。
如果在observer的時(shí)候,對(duì)state中的每一項(xiàng)增添一個(gè)指向父節(jié)點(diǎn)的指針,在后代節(jié)點(diǎn)變動(dòng)時(shí),是不是就能沿著指向父節(jié)點(diǎn)的指針找到相應(yīng)的根節(jié)點(diǎn)了?
為避免新增的指針被遍歷到,決定采用Symbol,于是dep部分變動(dòng)如下:
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)
}
...
}
})
}
再加個(gè)可以找到根節(jié)點(diǎn)的方法,就可以改變對(duì)應(yīng)storage項(xiàng)了
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]就是根節(jié)點(diǎn),storagePath記錄了從根節(jié)點(diǎn)到變動(dòng)節(jié)點(diǎn)的路徑
return storagePath
}
但是問(wèn)題又來(lái)了,object是可以實(shí)現(xiàn)自動(dòng)持久化了,數(shù)組用push、pop這些方法操作時(shí),數(shù)組的地址是沒(méi)有變動(dòng)的,defineProperty根本監(jiān)測(cè)不到這種地址沒(méi)變的情況(可惜Proxy兼容性太差,小程序中安卓直接不支持)。當(dāng)然,每次操作數(shù)組時(shí),對(duì)數(shù)組重新賦值可以解決此問(wèn)題,但是用起來(lái)太不方便了。
改變數(shù)組時(shí)的雙向綁定
數(shù)組的問(wèn)題,解決方式一樣是參照vue源碼的處理,重寫(xiě)數(shù)組的'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'方法
數(shù)組用這7種方法操作數(shù)組的時(shí)候,手動(dòng)觸發(fā)set中部分,更新storage內(nèi)容
添加防抖
vuex持久化時(shí),容易遇到頻繁操作state的情況,如果一直更新storage,性能太差
實(shí)現(xiàn)代碼
最后代碼如下:
tool.js:
/*
持久化相關(guān)內(nèi)容
*/
// 重寫(xiě)的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 ('參數(shù)需為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 ('參數(shù)需為object')
let index
Object.keys(obj).forEach(key => {
dep(obj, key, options)
index = typeArr.indexOf(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]
updateItems.add(changeKey)
clearTimeout(timer)
timer = setTimeout(() => {
try {
updateItems.forEach(key => {
fn.call(this, key, state[key])
})
updateItems.clear()
} catch (e) {
console.error(`persistent.js中state內(nèi)容持久化失敗,錯(cuò)誤位于[${changeKey}]參數(shù)中的[${key}]項(xiàng)`)
}
}, 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自動(dòng)配置mutation相關(guān)方法
*/
export function setMutations(stateReplace, mutationsReplace) {
Object.keys(stateReplace).forEach(key => {
let name = key.replace(/\w/, (first) => `update${first.toUpperCase()}`)
let replaceState = (key, state, payload) => {
state[key] = payload
}
mutationsReplace[name] = (state, payload) => {
replaceState(key, state, payload)
}
})
}
/*
通用方法
*/
export function getType(para) {
return Object.prototype.toString.call(para)
.replace(/\[object (.+?)\]/, '$1').toLowerCase()
}
persistent.js中調(diào)用:
import {persistedState} from '../common/tools.js'
...
...
// 因?yàn)槭莡ni-app小程序,持久化是調(diào)用uni.setStorageSync,網(wǎng)頁(yè)就用localStorage.setItem
persistedState({state, setItem: uni.setStorageSync, setDelay: 1000})
源碼地址
https://github.com/goblin-pitcher/uniapp-miniprogram
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vite項(xiàng)目添加eslint?prettier及husky方法實(shí)例
這篇文章主要為大家介紹了vite項(xiàng)目添加eslint?prettier及實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
vue 解決provide和inject響應(yīng)的問(wèn)題
這篇文章主要介紹了vue 解決provide和inject響應(yīng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
vue history 模式打包部署在域名的二級(jí)目錄的配置指南
這篇文章主要介紹了vue history 模式打包部署在域名的二級(jí)目錄的配置指南 ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07

