背景
最近用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 (.+"htmlcode">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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
vuex,持久化工具
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
P70系列延期,华为新旗舰将在下月发布
3月20日消息,近期博主@数码闲聊站 透露,原定三月份发布的华为新旗舰P70系列延期发布,预计4月份上市。
而博主@定焦数码 爆料,华为的P70系列在定位上已经超过了Mate60,成为了重要的旗舰系列之一。它肩负着重返影像领域顶尖的使命。那么这次P70会带来哪些令人惊艳的创新呢?
根据目前爆料的消息来看,华为P70系列将推出三个版本,其中P70和P70 Pro采用了三角形的摄像头模组设计,而P70 Art则采用了与上一代P60 Art相似的不规则形状设计。这样的外观是否好看见仁见智,但辨识度绝对拉满。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]