实现一个简易版的vuex持久化工具
背景
最近用uni-app开发小程序项目时,部分需要持久化的内容直接使用vue中的持久化插件貌似不太行,所以想着自己实现一下类似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),即当某一项的后代节点变动时,我们需要沿着变动的后代节点找到它的根节点,然后将根节点下的内容全部替换成新的。
接下来的第一个问题就是,如何找到变动节点的祖先节点。
state树的重新构造
方案一:沿着state向下找到变动的节点,根据寻找路径确认变动项的根节点,此方案复杂度太高。
方案二:在observer的时候,对state中的每一项增添一个指向父节点的指针,在后代节点变动时,可以沿着指向父节点的指针找到相应的根节点,此方案可行。
为避免新增的指针被遍历到,决定采用Symbol标记指针,于是dep部分变动如下:
const pointerParent = Symbol('parent') const poniterKey = Symbol('key') function dep(obj, key, options) { let data = obj[key] if (getType(data)==='object') { data[pointerParent] = obj data[poniterKey] = key } Object.defineProperty(obj, key, { configurable: true, get() { ... }, set(val) { if (val === data) return data = val if(getType(data)==='object') { data[pointerParent] = obj data[poniterKey] = key observer(data) } ... } }) }
再加个可以找到根节点的方法,就可以改变对应storage项了
function getStoragePath(obj, key) { let storagePath = [key] while (obj) { if (obj[poniterKey]) { key = obj[poniterKey] storagePath.unshift(key) } obj = obj[pointerParent] } // 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'] // 各级指向父节点和及父节点名字的项 const pointerParent = Symbol('parent') const poniterKey = Symbol('key')