如何实现一个简易版的vuex持久化工具

 更新时间:2019年09月11日 09:38:02   作者:goblin_pitcher  
这篇文章主要介绍了实现一个简易版的vuex持久化工具,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

背景

最近用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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 如何构建一个Vue插件并生成npm包

    如何构建一个Vue插件并生成npm包

    这篇文章主要介绍了如何构建一个Vue插件并生成npm包,帮助大家更好的理解和使用vue,方便以后的开发,感兴趣的朋友可以了解下
    2020-10-10
  • Vue列表页渲染优化详解

    Vue列表页渲染优化详解

    这篇文章主要为大家详细介绍了Vue列表页渲染优化的操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • Vue (Vuex)中 store 基本用法

    Vue (Vuex)中 store 基本用法

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,这篇文章主要介绍了Vue 中 store 基本用法,需要的朋友可以参考下
    2023-01-01
  • 详解Vue计算属性原理

    详解Vue计算属性原理

    计算属性是Vue中比较好用的API,开发者可以利用计算属将复杂的计算进行缓存,同时基于它的响应式特性,我们无需关注数据更新问题,但需要注意的是,计算属性是惰性求值的,本文将详细介绍计算属性的实现原理,需要的朋友可以参考下
    2023-05-05
  • vue中{{}},v-text和v-html区别与应用详解

    vue中{{}},v-text和v-html区别与应用详解

    这篇文章主要介绍了vue中{{}},v-text和v-html区别与应用详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • Vite处理html模板插件之vite-plugin-html插件使用

    Vite处理html模板插件之vite-plugin-html插件使用

    这篇文章主要给大家介绍了关于Vite处理html模板插件之vite-plugin-html插件使用的相关资料,Vite是一个现代化的前端构建工具,而vite-plugin-html是Vite的一个插件,用于在构建时自动生成HTML文件,需要的朋友可以参考下
    2023-10-10
  • vue2.0使用swiper组件实现轮播的示例代码

    vue2.0使用swiper组件实现轮播的示例代码

    下面小编就为大家分享一篇vue2.0使用swiper组件实现轮播的示例代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • 脚手架(vue-cli)创建Vue项目的超详细过程记录

    脚手架(vue-cli)创建Vue项目的超详细过程记录

    用vue-cli脚手架可以快速的构建出一个前端vue框架的项目结构,下面这篇文章主要给大家介绍了关于脚手架(vue-cli)创建Vue项目的超详细过程,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • Vant 中的Toast设置全局的延迟时间操作

    Vant 中的Toast设置全局的延迟时间操作

    这篇文章主要介绍了Vant 中的Toast设置全局的延迟时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • uniapp+vue3路由跳转传参的实现

    uniapp+vue3路由跳转传参的实现

    本文主要介绍了uniapp+vue3路由跳转传参的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11

最新评论