vue数据自动存储的一个小Demo

一年前端菜鸟,开始记录一下自己学习的心得体会,欢迎指点,大神轻喷

最近碰到一个需求,需要存储每个页面的数据,刷新之后也要有,因此写了一个自动存储数据的mixin

简单的实现思路:

vue对data里面的数据通过Object.defineProperty做了双向绑定,我们可以通过Reflect.getOwnPropertyDescriptor去拿到vue定义的描述符对象,也就拿到了get,set函数,通过定义自己的get,set函数做一些想要的扩展,比如存储数据,然后代理vue的get,set,重新defineProperty。

下面上代码:

// 先写一个工具函数用来判断对象和数组
const createJudgeType = typeStr => obj => Object.prototype.toString.call(obj) === `[object ${type}]`
// 创建判断对象和数组的函数
const isObject = createJudgeType('Object')
const isArray = createJudgeType('Array')  // 可以使用Array.isArray
// 定义数组变异方法数组后面会使用
const arrayMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse',
]
class AutoSave {
  // 判断__proto__能否使用
  static hasProto = Reflect.has({}, '__proto__')
  // 实例化
  static of (vm) {
    return new AutoSave(vm)
  }
  constructor (vm) {
    this.vm = vm // 当前vue实例
    this.arrayProto = null // 根据hasProto来决定是vue定义的数组原型还是当前数组本身
    this.init()
  }
  init () {
    // 先判断是否需要这个功能
    if (this.isKeepData()) {
      const data = this.getData()
      // 如果数据存在初始化数据否则存储数据
      data ? this.initData(data) : this.setData()
      this.reactive(this.vm.$data, this.vm)
    }
  }
  initData (str) {
    // 初始化数据
    const data = JSON.parse(str)
    Object.assign(this.vm, data)
  }
  isKeepData () {
    // 这里可以做一些判断来决定是否要存储数据
    // 比如通过路由meta配置变量,
    return this.vm.$route.meta.keepData
  }
  getData () {
    // 获取当前路由对应的data
    const routeName = this.vm.$route.name
    return sessionStorage.getItem(routeName)
  }
  setData () {
    // 通过当前路由名字作为键去存储这个路由对应组件的data
    const routeName = this.vm.$route.name
    sessionStorage.setItem(routeName, JSON.stringify(this.vm.$data))
  }
  reactive (obj, vm) {
    // 如果data是一个深层对象,只有第一次调用的时候会传递vm实例
    // 原因是vue在vm实例上对data对象的每个属性做了一层代理,即this.a实际上访问的是this._data.a
    const target = vm || obj
    Object.entries(obj).forEach(([key, val]) => {
      // 遍历obj给每个属性设置自己的代理
      this.proxy(target, key, val)
      //继续判断每个值
      this.judgeType(val)
    })
  }
  proxy (target, key, val) {
    // 获取vue的描述符对象,定义自己的描述符对象
    const {
      get: vGet,
      set: vSet,
    } = Reflect.getOwnPropertyDescriptor(target, key)
    const that = this
    const descriptor = {
      enumerable: true,
      configurable: true,
      get: vGet.bind(target),
      set (value) {
        vSet.call(target, value)
        that.setData()
      },
    }
    Reflect.defineProperty(target, key, descriptor)
  }
  judgeType (val) {
    //如果是对象递归调用reactive
    isObject(val) && this.reactive(val)
    //如果是数组则代理数组
    isArray(val) && this.proxyArray(val)
  }
  def (target, key, val, enumerable) {
    Reflect.defineProperty(target, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true,
    })
  }
  createArrayProto (arr) {
    // 如果__proto__可用 并且数组新的原型对象已经存在直接返回
    if (AutoSave.hasProto && this.arrayProto) {
      return this.arrayProto
    }
    // 如果__proto__可用,获取vue定义的数组原型,否则拿到数组本身,以此创建新的原型对象
    // 并给这个原型对象定义自己的变异方法,这个变异方法里面调用了存储数据的功能和vue的变异方法
    const vArrayProto = AutoSave.hasProto ? Reflect.getPrototypeOf(arr) : arr
    this.arrayProto = Object.create(vArrayProto)
    const that = this
    arrayMethods.forEach(method => {
      this.def(this.arrayProto, method, function (...args) {
        const result = vArrayProto[method].call(this, ...args)
        that.setData()
        return result
      })
    })
    return this.arrayProto
  }
  proxyArrayProto (target, src) {
    /* eslint-disable */
    // 如果__proto__可用,直接给当前数组设置一个新的原型
    target.__proto__ = src
  }
  proxyArrayMethods (target, src) {
    // 如果__proto__不可用,在当前数组上定义新的变异方法,这个变异方法里面调用了存储数据的功能和vue的变异方法
    arrayMethods.forEach(method => {
      this.def(target, method, src[method])
    })
  }
  proxyArray (arr) {
    // 根据__proto__是否可用来决定对数组的处理方式
    const handle = AutoSave.hasProto ? this.proxyArrayProto : this.proxyArrayMethods
    handle(arr, this.createArrayProto(arr))
    // 循环数组递归判断里面的每一项
    arr.forEach(item => this.judgeType(item))
  }
}
export default {
  beforeRouteEnter (to, from, next) {
    next(vm => AutoSave.of(vm))
  }
}
复制代码

由于beforeRouteEnter会在每次路由切换执行所以需要适当优化,比如换成created

应该有更好的解决方案,欢迎大神指点

转载于:https://juejin.im/post/5bf67275e51d45769b04ccb2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值