学会了Vuex与Cookies、localStorage、sessionStorage联动实现数据持久化

167 阅读3分钟

前言

在Vue的日常的开发过程中,我们难免会使用上Vuex与浏览器的缓存,Vuex在每次页面重新页面后都会重新的加载Vuex里面的数据,一不小心就又回到了初始值。所以后面一个办法利用浏览器缓存将数据缓存起来。页面刷新的时候去检查缓存中是否存在如果存在就去缓存中的值。这就出现了一要更新Vuex中的值又要更新缓存中的值,久而久之觉得这种方法太麻烦了,需要我就去逛了逛了Vuex官网,发现一个重大的功能,Vuex是有拓展性的,可以自行添加插件功能

image.png

更多详情查阅官网

有了这个就好办了,可以将Vuex将浏览器缓存起来,将需要同时保存到缓存中的数据进行同步。 譬如像这样

image.png

image.png

这样上面的val表示的是vuex中的state数据,有user模块下的数据,key表示的是需要缓存道浏览器中的key值,type表示的是调用的store.commit中的方法,其实dispatch也是可以的,只有写对就行。

在完成上面的功能前,我们先将Cookies、localStorage、sessionStorage封装一下。 在封装的时候,加上前缀的,主要是看个人的需求吧。这里主要是项目名+版本号+key

cookies.js

import jsCookies from "js-cookie"
export class Cookies {
  static prefix
  static version

  constructor(options) {
    if (options && JSON.stringify(options) !== '{}') {
      if (options.prefix) this.prefix = options.prefix
      if (options.version) this.version = options.version
    } else {
      this.prefix =
        import.meta.env.VITE_TITLE
      this.version =
        import.meta.env.VITE_VERSION
    }
  }
  /**
   *  存储 cookie 值
   * @param name
   * @param value
   * @param cookieSetting
   */
  set(name = "default", value = "", cookieSetting = {}) {
    const currentCookieSetting = {
      expires: 1,
      path: import.meta.env.VITE_COOKIES_PATH,
      domain: import.meta.env.VITE_COOKIES_DOMAIN,
      ...cookieSetting
    };
    jsCookies.set(
      `${this.prefix}-${this.version}-${name}`,
      value,
      currentCookieSetting
    );
  }
  /**
   * 拿到 cookie 值
   * @param name
   * @returns
   */
  get(name = "default") {
    return jsCookies.get(
      `${this.prefix}-${this.version}-${name}`
    );
  }
  /**
   * 拿到 cookie 全部的值
   * @returns
   */
  getAll() {
    return jsCookies.get();
  }
  /**
   * 删除 cookie
   * @param name
   */
  remove(name = "default", cookieSetting) {
    // console.log("移除cookies",name)
    const options = {
      path: import.meta.env.VITE_COOKIES_PATH,
      domain: import.meta.env.VITE_COOKIES_DOMAIN,
      ...cookieSetting
    }
    jsCookies.remove(
      `${this.prefix}-${this.version}-${name}`, {
        ...options
      }
    );
  }
  /**
   * 
   * @param {*} name key值
   * @param {*} version 版本号
   * @param {*} cookieSetting 多余的参数
   * @param {*} prefix 前缀
   */
  removeVersion(name = "default", version, prefix) {

    const options = {
      path: import.meta.env.VITE_COOKIES_PATH,
      domain: import.meta.env.VITE_COOKIES_DOMAIN,
    }
    // console.log("移除removeVersion", name, version, options, `${prefix?prefix:this.prefix}-${version}-${name}`)
    jsCookies.remove(
      `${prefix?prefix:this.prefix}-${version}-${name}`, {
        ...options
      }
    );
  }
  removeAll(cookieSetting) {
    var all = this.getAll()
    const options = {
      path: import.meta.env.VITE_COOKIES_PATH,
      domain: import.meta.env.VITE_COOKIES_DOMAIN,
      ...cookieSetting
    }
    for (let i in all) {
      jsCookies.remove(i,{...options});
    }
  }
}

export default new Cookies();

cache.js

class CommonLocalStorage {
    constructor(storageModel) {
        this.storage = storageModel;
        this.prefix = import.meta.env.VITE_TITLE
        this.version = import.meta.env.VITE_VERSION
    }
    setItem(key, value) {
        // 执行监听的操作
        return this.storage.setItem(`${this.prefix}-${this.version}-${key}`, value);
    }
    getItem(key) {
        // 执行监听的操作
        return this.storage.getItem(`${this.prefix}-${this.version}-${key}`);
    }
    removeItem(key) {
        // 执行监听的操作
        return this.storage.removeItem(`${this.prefix}-${this.version}-${key}`);
    }
    removeVersion(key,version,prefix) {
        // 执行监听的操作
        return this.storage.removeItem(`${prefix?prefix:this.prefix}-${version}-${key}`);
    }
    clear() {
        // 执行监听的操作
        this.storage.clear();
    }
}

export const storageSession = new CommonLocalStorage(sessionStorage);
export const storageLocal = new CommonLocalStorage(localStorage);

checkFailureCacheAndCookies.js

在我们修改版本号或者是项目名的时候,我们就需要将原先的缓存清除掉。不然日积月累的情况下,很容易的就会满的,比较容量是有限的。

这个只在放在入口文件中引用即可

import * as storage from "@/utils/cache"
import {
    Cookies
} from "@/utils/cookies"
/**
 * 
 * @param {*} version 版本号
 * @param {*} prefix 前缀
 * @param {*} type 类型:cookies、storageLocal、storageSession
 */

function checkFailureCacheAndCookies(options) {
    let type = ["cookies", "storageLocal", "storageSession"]
    options = {
        version: import.meta.env.VITE_VERSION,
        prefix: import.meta.env.VITE_TITLE,
        ...options
    }
    //校验key名的白名单
    let storageWhiteList = ["length", "clear", "getItem", "key", "removeItem", "setItem"]

    function clear(data, storage, type) {
        for (let i in data) {
            if (options.oldPrefix && options.oldPrefix !== options.prefix) {
                //不同前缀,不管版本号,直接删除旧的
                let keyArr = i.split(options.oldPrefix)
                if (keyArr.length <= 1) return
                keyArr = keyArr[1].split("-")
                storage.removeVersion(keyArr[2], keyArr[1], options.oldPrefix)
            } else {
                if (type == "storage" && storageWhiteList.includes(i)) return
                //同个前缀检查版本号,不一致就删除不同版本号的
                let keyArr = i.split(options.prefix)
                // console.log(options.prefix,keyArr,keyArr.length <= 1)
                if (keyArr.length <= 1) return
                keyArr = keyArr[1].split("-")
                
                // console.log(keyArr,options.version,keyArr[1] !== options.version)
                if (keyArr[1] !== options.version) {
                    storage.removeVersion(keyArr[2], keyArr[1])
                }
            }
        }
    }

    //用于清除其他中台的cookies缓冲
    if (options.type === "cookies") {
        const cookies = new Cookies()
        let data = cookies.getAll()
        clear(data, cookies, "cookies")
    } else {
        for (let t of type) {
            if (t === 'cookies') {
                const cookies = new Cookies()
                let data = cookies.getAll()
                clear(data, cookies, "cookies")
            } else {
                let data = storage[t].storage
                clear(data, storage[t], "storage")
            }
        }
    }
}
// checkFailureCacheAndCookies()
export default checkFailureCacheAndCookies

封装完我们的浏览器缓存部分,就开始来封装我们的插件功能,这里仅是封装了读取或者获取的功能,有想要其他的功能拓展的可以自行拓展。

persistedstate.js

import {
    storageSession,
    storageLocal
} from "@/utils/cache"
export default function persistedstate(options) {
    options = options || {};
    const storage = options.storage || storageLocal;
    const key = options.key;
    if(!key){
        throw new Error("请先添加key")
    }
    if(!options.type){
        throw new Error("请先添加mutation中的type")
    }

    function getState(key, storage) {
        const value = storage.getItem(key);
        try {
            return (typeof value === "string") ?
                JSON.parse(value) : (typeof value === "object") ?
                value : undefined;
        } catch (err) {}

        return undefined;
    }

    function setState(key, state, storage) {
        // return storage.setItem(key, JSON.stringify(state));
        state = JSON.stringify(state)
        // console.log("设置时候的key",key,state,state=='""',state == "{}",state == "[]") 
        if(state && state !== "{}" && state !== "[]" && state !== '""'){
            return storage.setItem(key, state);
        }else{
            removeState(key)
        }
    }

    function reducer(state, mutation) {
        return state;
    }

    function subscriber(store) {
        return function (handler) {
            return store.subscribe(handler);
        };
    }

    function removeState(key){
        // if(storage.isCookies){
        //     // console.log("移除的key",key,storage)
        //     storage.removeItem(key,{path:import.meta.env.VITE_COOKIES_PATH,domain:import.meta.env.VITE_COOKIES_DOMAIN}); 
        // }else{
        //     storage.removeItem(key);
        // }
        storage.removeItem(key);
    }

    const assertStorage =
        options.assertStorage ||
        (() => {
            storage.setItem("@@", 1);
            removeState("@@")
        });

    assertStorage(storage);

    const fetchSavedState = () => (options.getState || getState)(key, storage);

    let savedState;

    if (options.fetchBeforeUse) {
        savedState = fetchSavedState();
    }

    return function (store) {
        
        if (!options.fetchBeforeUse) {
            savedState = fetchSavedState();
        }
        // console.log("vuex插件初始化", store,savedState);
        store.commit(options.type, savedState);

        (options.subscriber || subscriber)(store)(function (mutation, state) {
            if(options.type === mutation.type){
                // console.log(state,mutation);
                (options.setState || setState)(
                    key,
                    (options.reducer || reducer)(state,mutation),
                    storage
                );
            }
            
        });
    };
}

应用插件功能

///dataPlugins.js
import persistedstate from "@/utils/persistedstate"
import Cookies from '@/utils/cookies';
const cookies = {
    getItem: (key) => Cookies.get(key),
    setItem: (key, value, options) => Cookies.set(key, value, {
        path:import.meta.env.VITE_COOKIES_PATH,
        domain: import.meta.env.VITE_COOKIES_DOMAIN,
        ...options
    }),
    removeItem: (key, options) => Cookies.remove(key, {
        path:import.meta.env.VITE_COOKIES_PATH,
        domain: import.meta.env.VITE_COOKIES_DOMAIN,
        ...options
    }),
    // isCookies:true
}
const dataPlugins = [
    persistedstate({
        key: "userInfo",
        type: "user/setUserInfo",
        reducer(val) { // 注意: 指定需要持久化的数据
            // console.log('userInfo', val)
            return val.user.userInfo
        }
    }),
    persistedstate({
        key: "userId",
        type: "user/setUserId",
        reducer(val) { // 注意: 指定需要持久化的数据
            // console.log('userInfo', val)
            return val.user.userId
        },
        storage: {
            ...cookies
        }
    }),
]
///store/index.js
import dataPlugins from "./dataPlugins"
const store = new createStore({
  plugins: dataPlugins
});

export default store;

其中这个storage缓存的对象也是可以自定义的。

总结

革命尚未成功,还需努力。

需要拓展的功能的,有兴趣可以自己研究一下。