vue資料自動儲存的一個小Demo

我上來就是一jo發表於2018-11-22

一年前端菜鳥,開始記錄一下自己學習的心得體會,歡迎指點,大神輕噴

最近碰到一個需求,需要儲存每個頁面的資料,重新整理之後也要有,因此寫了一個自動儲存資料的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

應該有更好的解決方案,歡迎大神指點

相關文章