您好,登錄后才能下訂單哦!
背景
最近用uni-app開發(fā)小程序項目時,部分需要持久化的內(nèi)容沒法像其他vuex中的state那樣調(diào)用,所以想著自己實現(xiàn)一下類似vuex-persistedstate插件的功能,貌似代碼量也不會很大
初步思路
首先想到的實現(xiàn)方式自然是vue的watcher模式。對需要持久化的內(nèi)容進行劫持,當(dāng)內(nèi)容改變時,執(zhí)行持久化的方法。
先弄個dep和observer,直接observer需要持久化的state,并傳入get和set時的回調(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)問題,若將a={b:{c:d:{e:1}}}存入storage,操作一般是xxstorage('a',a),接下來無論是改了a.b還是a.b.c或是a.b.c.d.e,都需要重新執(zhí)行xxstorage('a',a),也就是無論a的哪個后代節(jié)點變動了,重新持久化的都是整個object樹,所以監(jiān)測到某個根節(jié)點的后代節(jié)點變更后,需要先找到根節(jié)點,再將根節(jié)點對應(yīng)的項重新持久化。
接下來的第一個問題就是,如何找到變動節(jié)點的父節(jié)點。
state樹的重新構(gòu)造
如果沿著state向下找到變動的節(jié)點,并根據(jù)找到節(jié)點的路徑確認變動項,復(fù)雜度太高。
如果在observer的時候,對state中的每一項增添一個指向父節(jié)點的指針,在后代節(jié)點變動時,是不是就能沿著指向父節(jié)點的指針找到相應(yīng)的根節(jié)點了?
為避免新增的指針被遍歷到,決定采用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) } ... } }) }
再加個可以找到根節(jié)點的方法,就可以改變對應(yīng)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]就是根節(jié)點,storagePath記錄了從根節(jié)點到變動節(jié)點的路徑 return storagePath }
但是問題又來了,object是可以實現(xiàn)自動持久化了,數(shù)組用push、pop這些方法操作時,數(shù)組的地址是沒有變動的,defineProperty根本監(jiān)測不到這種地址沒變的情況(可惜Proxy兼容性太差,小程序中安卓直接不支持)。當(dāng)然,每次操作數(shù)組時,對數(shù)組重新賦值可以解決此問題,但是用起來太不方便了。
改變數(shù)組時的雙向綁定
數(shù)組的問題,解決方式一樣是參照vue源碼的處理,重寫數(shù)組的'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'方法
數(shù)組用這7種方法操作數(shù)組的時候,手動觸發(fā)set中部分,更新storage內(nèi)容
添加防抖
vuex持久化時,容易遇到頻繁操作state的情況,如果一直更新storage,性能太差
實現(xiàn)代碼
最后代碼如下:
tool.js:
/* 持久化相關(guān)內(nèi)容 */ // 重寫的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)容持久化失敗,錯誤位于[${changeKey}]參數(shù)中的[${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相關(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' ... ... // 因為是uni-app小程序,持久化是調(diào)用uni.setStorageSync,網(wǎng)頁就用localStorage.setItem persistedState({state, setItem: uni.setStorageSync, setDelay: 1000})
源碼地址
https://github.com/goblin-pitcher/uniapp-miniprogram
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。