前端的平常開發中,常常須要使用到localStorage存儲,在使用Vue做爲開發框架時,但願能與Vue的響應式系統集成到一塊兒,能夠像vue-router/vuex相似的模式使用。前端
vue的響應式原理,是爲一個對象添加特定的屬性描述符,劫持它全部屬性的getter/setter。 在這裏,咱們定義一個對象_storage
,遍歷它的全部屬性,經過官方暴露出的方法——Vue.util.defineReactive,把_storage
變成observable
了,那麼只要修改_storage某個key
的值,依賴(模板/數據)就能實時更新。vue
接下來,咱們須要把_storage
對象與本地的localStorage
作實時同步。粗糙的辦法是每次更新_storage
時手動更新localStorage
,這就不符合咱們作這個插件的初衷了,咱們的願景是自動同步。git
咱們也像Vue那樣使用Object.defineProperty
的功能,在getter/setter
裏調用對應的localStorage api
不就能解決問題了?但是這樣的話_storage
的每個屬性都被Object.defineProperty
兩次了,那麼前面一次就會被覆蓋,沒法生效啊!github
Vue.util.defineReactive
會保存前一次的
getter/setter
(若是有的話),在將來每一次的
getter/setter
中都會執行一次,在這裏咱們就能夠調用相關的
localStorage api
了。
配置項web
localStorage
中使用到的key-value
,且須要指定數據類型,由於保存在本地存儲的數據只能是字符串,因此取本地web存儲數據時要根據原來的數據類型進行解析,例如對象須要JSON.parse
key
的公共前綴namespace
,方便標識某個項目使用的web存儲const storage = Vue.use(Storage, 'my-namespace', { string: { type: String, default: 'test' }, number: { type: Number }, object: { type: Object, default: { hello: 'world' } } }) class Storage { static install (Vue, nameSpace, options) { if (typeof nameSpace === 'object') { options = nameSpace nameSpace = 'vue-storage' } return new Storage(Vue, nameSpace, options) } } 複製代碼
插件定義Storage
類,且提供一個install
方法給Vue進行註冊。key/value
提供type
和default
指定類型和默認值。vue-router
class Storage { constructor (Vue, nameSpace, options = {}) { const self = this this.Vue = Vue this.nameSpace = `${nameSpace}-` this.options = options // 刷新頁面時,把本地storage從新取出來 const cacheStorage = Object.keys(window.localStorage) .filter(key => new RegExp(`^${this.nameSpace}`).test(key)) .reduce((acc, key) => Object.assign(acc, { [ key.replace(this.nameSpace, '') ]: window.localStorage[key] }), {}) // 每種數據類型的默認值 const keyMap = [ [ String, '' ], [ Boolean, '' ], [ Number, '' ], [ Array, [] ], [ Object, {} ], ] const map = this.typeMap = new Map(keyMap) let _storage = this.storage = { ...( Object.keys(options).reduce((acc, key) => { const { type, default: val } = options[key] if (!type) { Vue.util.warn(`type of the field 'key' is required`) return acc } return Object.assign(acc, { [key]: val === undefined ? map.get(type) : val }) }, {}) ), ...cacheStorage } } 複製代碼
Storage
實例化的過程,先把本地web存儲的數據提取出來,合併到配置的key/value
中,實現頁面刷新不丟失數據。vuex
考慮到數據沒有定義默認值,經過keyMap
爲每種數據類型定義一個默認值。最終獲得一個_storage
對象,且賦值到this.storage
供外部實例調用。api
class Storage { constructor () { Object.keys(_storage).forEach(key => { try { const val = _storage[key] this.set(key, val) } catch (e) { Vue.util.warn('vue-storage-error', e) } // 把_storage中key對應的value取值代理到localStorage中去 Object.defineProperty(_storage, key, { get: () => self.get(key), set: (val) => self.set(key, val), configurable: true }) // 定義可觀察對象 Vue.util.defineReactive(_storage, key, _storage[key]) }) } get (key) { let val = window.localStorage.getItem(this._getKey(key)) val = this._parse(key, val) return val } set (key, val) { try { val = typeof val === 'object' ? JSON.stringify(val) : val window.localStorage.setItem(this._getKey(key), val) } catch (e) { Vue.util.warn(`storage setting fail, please check the value`) } } } 複製代碼
經過Object.defineProperty
,_storage
中key對應的value
的讀取/寫入實際是在localStorage
中去讀取/寫入。markdown
class Storage { constructor () { const self = this Object.defineProperty(Vue.prototype, '$storage', { get: () => _storage }) // 代理Storage實例 Object.defineProperty(Vue.prototype, '$storager', { get: () => self }) Vue.storage = _storage Vue.storager = self } } 複製代碼
參照vue-router
那樣,對Vue/Vue實例
暴露_storage
可觀察數據和Storage
實例,實如今組件內能夠經過this.$storage
獲取_storage
。框架
class Storage { get (key) { let val = window.localStorage.getItem(this._getKey(key)) val = this._parse(key, val) return val } set (key, val) { try { val = typeof val === 'object' ? JSON.stringify(val) : val window.localStorage.setItem(this._getKey(key), val) } catch (e) { Vue.util.warn(`storage setting fail, please check the value`) } } remove (key) { window.localStorage.removeItem(this._getKey(key)) } clear () { Object.keys(this.storage).forEach(key => { const lsKey = this._getKey(key) if (window.localStorage.hasOwnProperty(lsKey)) { window.localStorage.removeItem(lsKey) } }) } } 複製代碼
提供get/set/remove/clear
方法操做localStorage
。相似於getItem/setItem/removeItem/clear
。
完