VBox持續進行中,哀家苦啊,有沒有誰給個star。
Github位址:
https://github.com/xiangwenhu/vbox, 歡迎大家點贊
vuex是vue用于資料存儲的,和redux充當同樣的角色。
最近在VBox開發的時候遇到的問題,頁面重新整理或者關閉浏覽器再次打開的時候資料歸零。這是頭疼的問題。
網上搜,大家的方案都是把資料轉移到 localStorage或者其他持久化存儲(例如indexDB)。
這倒是可以,我在設計之初因為匆忙,沒有考慮周全,這下好,然不成每個 mutation都去存一下。
這個弄的我很不開心,周六在公司,本來就困的要死,又想不到合理的解決方案,昏昏沉沉睡着了。
醒了後,最初想采用 柯裡化和高階函數來解決這個問題,很可惜,沒有正解。
最小化修改,又不想動現有代碼,代理二字最為不過。記得上次我寫IBook之初,也用Proxy來攔截修改,同時存資料到磁盤檔案。
沒錯方案就是 ES6的Proxy,嘗試之後,确實是可以的。
源碼位址:
https://github.com/xiangwenhu/vbox/tree/master/src/utils這裡有兩個問題
1. 初始值的問題。
2. 我要可以配置哪些字段需要持久化,store裡面的資料,不代表我都需要持久化。
首先解決是 localStorage存儲的問題,因為需要轉換字元串,簡單封裝一個 LStorage.js,當然你也可以用
https://github.com/tsironis/lockr , https://github.com/nbubna/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,或者初始化就沒有設定預設值,是不會被監聽到的,應該是放到一級屬性監聽裡面, 進行一個判斷
不知道各位大神有什麼好的方法,可以分享出來。
參考文章: