如何实现一个简易版的vuex持久化工具
作者:goblin_pitcher 发布时间:2024-04-30 10:34:29
背景
最近用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 < 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内容持久化失败,错误位于[${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(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中调用:
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
来源:https://segmentfault.com/a/1190000020353367
猜你喜欢
- 可切片使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMIT 和OFFSET 子句。>>> E
- 前言在以前,商业分析对应的英文单词是Business Analysis,大家用的分析工具是Excel,后来数据量大了,Excel应付不过来了
- 昨天晚上睡觉前突然想到的,在此记一笔。传统方式以前我们做文章系统或新闻发布系统的时候,做文章内链(标签)的时候,通常是通过以下方式来实现的:
- Python是我喜欢的语言,简洁,优美,容易使用。前两天,我很激昂的向朋友宣传Python的好处。听过之后,朋友问我:好吧,我承认Pytho
- 阅读上一篇:W3C优质网页小贴士(一) 使用 alt 属性描述每幅图像alt 属性有什么用?alt 属性可以在一系列标签中使用(如
- Acunetix Web Vulnerability Scanner 是一款国外产的及其优秀的扫描工具,可以帮忙挖掘网站内的诸多漏洞,包括常
- 一. 介绍一个计数器工具提供快速和方便的计数,Counter是一个dict的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一
- python数据拟合主要可采用numpy库,库的安装可直接用pip install numpy等。1. 原始数据:假如要拟合的数据yyy来自
- 在 MySQL下,在进行中文模糊检索时,经常会返回一些与之不相关的记录,如查找 "%a%" 时,返回的可能有中文字符,却
- RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用。RSS搭建了信息迅速传
- 这篇文章主要介绍了python实现括号匹配方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- 本文实例讲述了php生成curl命令行的方法。分享给大家供大家参考,具体如下:示例:curl "http://localhost/
- 引言使用python接口来运行caffe程序,主要的原因是python非常容易可视化。所以不推荐大家在命令行下面运行python程序。如果非
- 目录1、切片的基础用法2、切片的高级用法3、自定义对象实现切片功能3.1、魔术方法:`getitem()`3.2、自定义序列实现切片功能3.
- 本文实例讲述了Python mutiprocessing多线程池pool操作。分享给大家供大家参考,具体如下:python — mutipr
- 关于Keras中,当数据比较大时,不能全部载入内存,在训练的时候就需要利用train_on_batch或fit_generator进行训练了
- django在引入第三方模块的时候保证服务的高可用,要设立一个备份接口,当主接口宕机时可以设置一个超市参数来使用备份的接口。nginx反向代
- Yahoo发布了一款基于FireFox的插件,名叫YSlow,这个插件可以分析网站的页面,并告诉你为了提高网站性能,如何基于某些规则而进行优
- 1. pyecharts 模块介绍Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。
- asp之家补充两点,以让大家看的更明白:一.什么是GUID?由于水平有限在看到这篇文章时,我并不了解什么是GUID,为了看懂文章当然先请教一