前端Storage管理封装思考

297 阅读3分钟

需求背景

旧项目在跳转到登录页时,会清空浏览器中的所有本地存储(SessionStorageLocalStorage),从而影响到该域名下的其他应用。

解决方案

解决方案很简单,只要给项目使用到的Storage附加唯一命名空间,并且在登录页的时候只清空当前命名空间下的Storage存储。

分析现状

  • 项目中封装了RootStorage,特性请看【旧api】
  • 存在部分数据直接调用Storage原生api进行读取操作,且没有命名空间标识
  • 部分存储使用了RootStorage进行管理,但存在多个命名空间
  • 项目中频繁调用new RootStorage(namespace) 创建新的实例,其实做的都是相同的操作
  • 有些数据如projectId,需要同时存储在LocalStorgeSessionStorage时,就出现了一些别扭的代码逻辑来处理,如下:

总结:经过分析,一部分原因是使用不规范,另一部分原因是RootStorage设计不合理。

RootStorageapi设计

/**
 * doc:
 *  RootStorage {
 *    name // nameSpace
 *    type // 'local'/'session'
 *  }
 *
 *  const myLocalStorage = new RootStorage({ name: 'my', type: 'local'})
 *  const mySessionStorage = new RootStorage({ name: 'my', type: 'session'})
 *
 *  myLocalStorage.set('key', 'value')
 *  myLocalStorage.set({ key1: 'value1', key2: 'value2'}) // 解构
 *  myLocalStorage.set(['value1', 'value2']) // 解构
 *  myLocalStorage.get('aa')
 *  myLocalStorage.remove('aa')
 *  myLocalStorage.clear() // 清空当前实例
 *  myLocalStorage.clear(true) // 清空整个loalstorage
 *
 *  mySessionStorage.set('key', 'value')
 *  mySessionStorage.get('aa')
 *  mySessionStorage.remove('aa')
 *  mySessionStorage.clear() // 清空当前实例
 *
 */

功能特性:

  • 封装了RootStorage来管理storage的存储
  • 支持get操作的数据parse
  • 支持set操作的解构
  • 支持设置命名空间
  • 支持清空【某种类型(local/session)&&某个命名空间】下的所有的存储数据

RootStorageapi设计

/**
 *  const mystorage = RootStorage.createStorage('my') // my:
 *  const myDevStorage = mystorage.createStorage('dev') // my:dev:
 *  const myDevOfflineStorage = myDevStorage.createStorage('offline') // my:dev:offline:

 *  mystorage.setLocal('key', 'value')
 *  mystorage.getLocal('key')
 *  mystorage.removeLocal('key')
 
 *  mystorage.setSession('key', 'value')
 *  mystorage.getSession('key')
 *  mystorage.removeSession('key')
 
 *  mystorage.set('key', 'value') // all
 *  mystorage.get('key') // session > local
 *  mystorage.remove('key')
 
 *  mystorage.clear() // 清空当前命名空间下的所有storage存储
 */
 

功能特性:

  • 将命名空间的层级做了提升,创建实例时,作为唯一且必须的参数,且不能重复;
  • 一个命名空间下存在多种类型的存储,包括localStorage,sesstionStorage...
  • 一个项目应该有一个项目级的nameSpace,如 name:
  • 项目中模块支持子命名空间,如 name:subName:
  • 支持清空【某个命名空间】下的所有的存储数据

总结:重新设计后的api明显更能贴合实际的应用场景,且使用起来更方便便捷。

但是因为是稳定的版本,怕改动太多存在风险,所以最终采用了另一种曲线救国的方法。。

曲线救国

LocalStorageSessionStorage都是Storage类的实例

getItemsetItem都是Storage原型上的方法,我们直接往原型上添加新的方法,该方法将附加上指定的命名空间,具体实现如下:

// 注入 getSanbaItem,setSanbaItem 方法
const nameSpace = 'sanba:';
Storage.prototype.getSanbaItem = function (keyName, ...args) {
    const realKeyName = `${nameSpace}${keyName}`;
    return this.getItem(realKeyName, ...args);
};
Storage.prototype.setSanbaItem = function (keyName, ...args) {
    const realKeyName = `${nameSpace}${keyName}`;
    this.setItem(realKeyName, ...args);
};
Storage.prototype.removeSanbaItem = function (keyName, ...args) {
    const realKeyName = `${nameSpace}${keyName}`;
    this.removeItem(realKeyName, ...args);
};

// 清空 datasanba 下的所有本地存储
export function clearSanbaStorage() {
    for (let i in window.localStorage) {
        if (Object.hasOwnProperty.call(window.localStorage, i)) {
            if (i.indexOf(nameSpace) === 0) {
                window.localStorage.removeItem(i);
            }
        }
    }
    for (let i in window.sessionStorage) {
        if (Object.hasOwnProperty.call(window.sessionStorage, i)) {
            if (i.indexOf(nameSpace) === 0) {
                window.sessionStorage.removeItem(i);
            }
        }
    }
}

大功告成,接下来只需要全局将getItem,setItem,clear方法替换掉即可,风险可控。