1. 程式人生 > >頁面重新整理vuex資料消失問題解決方案

頁面重新整理vuex資料消失問題解決方案


VBox持續進行中,哀家苦啊,有沒有誰給個star。

vuex是vue用於資料儲存的,和redux充當同樣的角色。

最近在VBox開發的時候遇到的問題,頁面重新整理或者關閉瀏覽器再次開啟的時候資料歸零。這是頭疼的問題。

網上搜,大家的方案都是把資料轉移到 localStorage或者其他持久化儲存(例如indexDB)。

這倒是可以,我在設計之初因為匆忙,沒有考慮周全,這下好,然不成每個 mutation都去存一下。

這個弄的我很不開心,週六在公司,本來就困的要死,又想不到合理的解決方案,昏昏沉沉睡著了。

醒了後,最初想採用 柯里化和高階函式來解決這個問題,很可惜,沒有正解。

最小化修改,又不想動現有程式碼,代理二字最為不過。記得上次我寫IBook之初,也用Proxy來攔截修改,同時存資料到磁碟檔案。

沒錯方案就是 ES6的Proxy,嘗試之後,確實是可以的。

這裡有兩個問題

1. 初始值的問題。

2. 我要可以配置哪些欄位需要持久化,store裡面的資料,不代表我都需要持久化。

複製程式碼
const ls = window.localStorage
// https://github.com/tsironis/lockr
export default {
  getItem(key) {
    try {
      return JSON.parse(ls.getItem(key))
    } catch (err) {
      return null
    }
  },
  setItem(key, val) {
    ls.setItem(key, JSON.stringify(val))
  },
  clear() {
    ls.clear()
  },
  keys() {
    
return ls.keys() }, removeItem(key) { ls.removeItem(key) } }
複製程式碼

其次就是代理的簡單封裝,LSproxy.js

這個版本還是有問題的,現在只能代理二級屬性,對現在的我而言已經是夠用了的。

createHanlder 建立二級屬性的代理

copy 複製物件,當然你可以寫更加相容優雅的方法

proxy  建立state的代理

複製程式碼
import LStorage from './LStorage'

/**
 * 代理二級屬性
 * @param {*} lsKey 存在localStorage的key
 * @param {*} pk    一級屬性的key
 
*/ function createHanlder(lsKey, pk) { return { set: function (target, key, value, receiver) { let item = LStorage.getItem(lsKey) if (item && item[pk]) { item[pk][key] = value LStorage.setItem(lsKey, item) } return Reflect.set(target, key, value, receiver) } } } /** * 僅僅存需要存放的資料 * @param {*} source * @param {*} keys */ function copy(source, keys = []) { if (!source) { return source } let d = Object.create(null) keys.forEach(k => { d[k] = source[k] }) return d } /** * 代理state * @param {*} initState 初始化的值 * @param {*} lsKey localStorage的key * @param {*} keys 需要儲存的鍵 */ const proxy = function (initState, lsKey, keys = []) { let ks = keys, obj = Object.assign({}, initState, LStorage.getItem(lsKey)) // 代理二級屬性 keys.forEach(k => { obj[k] = new Proxy(obj[k], createHanlder(lsKey, k)) }) // 存入合併的值 LStorage.setItem(lsKey, copy(obj, keys)) return new Proxy(obj, { set: function (target, key, value, receiver) { ks.indexOf(key) >= 0 && LStorage.setItem(lsKey, copy(target, keys)) return Reflect.set(target, key, value, receiver) } }) } export { proxy }
複製程式碼

呼叫這邊,基本就沒有什麼變化, 就多了一句  state = proxy(state, 'playing', ['list'])

複製程式碼
import { proxy } from '../utils/LSProxy'
let state = {
  list: [],
  current: null
}
state = proxy(state, 'playing', ['list'])

const mutations = {

  /**
   * 新增歌曲
   * @param {*} state 
   * @param {*} song 歌曲資訊 
   */
  addSong(state, song) {
    let index = state.list.findIndex(s => s.songmid === song.songmid)
    if (index < 0) {
      state.list.push(song)
    }
  },

  /**
   * 新增歌曲
   * @param {*} state  內建
   * @param {*} songs  歌曲列表
   */
  addSongs(state, songs) {
    let index = -1
    songs.forEach(song => {
      index = state.list.findIndex(s => s.songmid === song.songmid)
      if (index < 0) {
        state.list.push(song)
      }
    })
  },

  /**
   * 刪除歌曲
   * @param {*} state 
   * @param {*} songmid  歌曲媒體id 
   */
  removeSong(state, songmid) {
    let index = state.list.findIndex(s => s.songmid === songmid)
    index >= 0 && state.list.splice(index, 1)
  },

  /**
   * 批量刪除歌曲
   * @param {*} state 
   * @param {*} songmids 歌曲媒體列表 
   */
  removeSongs(state, songmids = []) {
    let index = -1
    songmids.forEach(songmid => {
      index = state.list.findIndex(s => s.songmid === songmid)
      index >= 0 && state.list.splice(index, 1)
    })
  },

  /**
   * 播放下一首,
   * @param {*} state 
   * @param {*} song 為空
   */
  next(state, song) {
    // 如果song不為空,表示是插放,(前提是已經新增到playing)
    if (song) {
      let index = state.list.findIndex(s => s.songmid === song.songmid)
      if (index >= 0) {
        state.current = state.list[index]
        return
      }
      return
    }
    // 如果current為空,表示沒有播放的歌曲
    if (!state.current && state.list && state.list.length > 0) {
      state.current = state.list[0]
      return
    }
    // 如果不是插放,並且current不為空
    if (!song && state.current) {
      // 播放的歌曲是不是在當前的列表
      let index = state.list.findIndex(s => s.songmid === state.current.songmid)
      // 如果在歌曲列表裡面,接著播放下首
      if (index >= 0) {
        state.current = (index === state.list.length - 1 ? state.list[0] : state.list[index + 1])
      } else {
        state.current = state.list[0]
      }
    }
  }
}

export default {
  namespaced: true,
  state,
  mutations
}
複製程式碼

這種方案的缺點也是很明顯的,

1. 程式碼只能代理二級,對我一般情況應該是夠用了,扁平化state

2. 代理二級屬性和陣列,要是屬性平凡修改的時候,代理是會重複觸發的,比如,新增30首歌曲的時候,是發生了30次儲存。 當然我覺得也是有方案可以優化的。

優點我覺得是,

1. state的資料與localStorage的同步過程分離開

2. 對現有程式碼的注入是相當少的。

當然我上面程式碼本身也還是存在問題的

1. 二級監聽不能在proxy執行的時候返回,因為如果屬性預設值為null/undefined,或者初始化就沒有設定預設值,是不會被監聽到的,應該是放到一級屬性監聽裡面, 進行一個判斷