Vuex数据持久化实现

版本:vue 3.4.29 vuex4.1.0

1. 出现的问题

当我使用 vuex 作为状态管理组件来存储用户的一些信息之后,发现从/login 页面跳转到/home 界面后拿不到vuex信息。
之后查阅资料了解,当切换路由后,vue 会重新渲染,而vuex 也会重新初始化,之前的状态就会全部丢失。因此需要对vuex 中的数据进行持久化。
以下展示了两种持久化方案:

2. 基于window监听的持久化方案

由于页面刷新后会导致vuex 重新初始化,因此可以通过监听页面刷新事件来持久化数据。对应的事件就是beforeunload。之后再在初始化的时候恢复数据即可。

App.vuescript 标签中编译一下代码即可实现:

<script setup>
import {useStore} from 'vuex'
const store = useStore()
// 恢复
if(localStorage.getItem("store")){
   store.replaceState(Object.assign({}, store.state, JSON.parse(localStorage.getItem("store"))))
}
// 存储
 window.addEventListener("beforeunload", () => {
   localStorage.setItem("store", JSON.stringify(store.state))
 })
</script>

3. 基于Vuex 插件的持久化方案

Vuex 官方提供了plugins 插件的选项,这个选项暴露出每次mutation 的钩子,可以让用户在每次 store 初始化和每次mutation 之后插入一些功能代码。因此可以作为持久化的实现方式。

Vuex 插件就是一个函数,它接受store 作为唯一参数

const myPlugin = (store) => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
  })
}

然后像下面一样使用:

const store = createStore({
  // ...
  plugins: [myPlugin]
})

持久化实现:
本项目中采用模块化的方式使用vuexstate 根据功能拆分,如涉及用户的状态数据放在user.js 里,统一放在modules 文件夹下。
在这里插入图片描述

index.js 文件:
首先使用import.meta.glob 读取modules文件下的所有文件,并将个模块放入到modules 对象中

// 一次性加载 modules 文件夹下的所有 store 文件
const files = import.meta.glob('./modules/*.js', {eager: true})
let modules = {}

// 遍历 files 对象, path为每个文件的路径
// Object.keys(object) 返回一个有对象属性名组成的数组
Object.keys(files).forEach((path) => {
    // 将 actions 、getters 等对象取出来
    const module = files[path].default
    // 将文件名取出来
    const modulesName = path.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2')
    modules[modulesName] = module
})

然后将modules 传入到自定义的插件中:

/**
 * 持久化 vuex 数据插件
 * 参数解释:
 *  key: 存储数据的键
 *  modules: 存储的具体数据模块
 *  modulesKeys: 存储数据的模块名数组
 *      这里分了两个区域:
 *          local 中的模块数据存在 localStorage 中
 *          session中的模块数据存在 session 中,这里为空,
 *          如果想存到这里可以自行设置
 *  建议用户相关的数据都存到 localStorage 里,因为 session 只针对当前会话,
 *  打开新的窗口就访问不到了
 */
const persistent = Presistent({key: 'vuex', modules, modulesKeys:{
    local: Object.keys(modules),
    session: []
}})

其中Presistent 就是自定义的插件工具,返回一个Vuex的插件函数给persistent 变量,之后就能使用了。

presistent.js 文件:
插件函数的具体实现如下:

export default function Presistent({key, modules, modulesKeys}){
    // 返回 vuex 的插件函数
    return (store) => {
         // 当 store 初始化后调用
        // 拿到存在 local 和 session 里的旧的数据
        const localOldState = JSON.parse(localStorage.getItem(key) || '{}')
        const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}')
        let oldState = {}
        // Object.assign() 静态方法将一个或者多个源对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象。
        // 如果有相同的属性名后者会覆盖前者,这里把两个数据源的数据合并到 oldState
        Object.assign(oldState, localOldState, sessionOldState)

        // 每次重启vuex 都将历史数据从缓存中恢复状态
        if(Object.keys(oldState).length > 0){
            for(const oldKey in oldState){
                modules[oldKey] = oldState[oldKey]
            }
            store.replaceState(modules)
        }

        store.subscribe((mutation, state) => {
          // 每次 mutation 之后调用
          // mutation 的格式为 { type, payload }

          // 将需要的模块存到 localStorage
          if(modulesKeys.local.length > 0){
            const localData = setData(store.state, modulesKeys.local)
            localStorage.setItem(key, JSON.stringify(localData))
          }else {
            localStorage.removeItem(key)
          }
          // 缓存到 session
          if(modulesKeys.session.length > 0){
            const sessionData = setData(store.state, modulesKeys.session)
            sessionStorage.setItem(key, JSON.stringify(sessionData))
          }else {
            sessionStorage.removeItem(key)
          }
        })
    }
}

function setData(state, module){
    let data = {}
    for(const i of module){
        data[i] = state[i]
    }
    return data
}

index.js 文件中的全部代码:

import { createStore } from 'vuex'
import Presistent from './presistent'

// 一次性加载 modules 文件夹下的所有 store 文件
const files = import.meta.glob('./modules/*.js', {eager: true})
let modules = {}

// 遍历 files 对象, path为每个文件的路径
// Object.keys(object) 返回一个有对象属性名组成的数组
Object.keys(files).forEach((path) => {
    // 将 actions 、getters 等对象取出来
    const module = files[path].default
    // 将文件名取出来
    const modulesName = path.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2')
    modules[modulesName] = module
})
console.log('moudles: ', modules)
const persistent = Presistent({key: 'vuex', modules, modulesKeys:{
    local: Object.keys(modules),
    session: []
}})


export default createStore({
    modules: {
        ...modules
    },
    plugins: [persistent]
})

参考资料:
插件实现代码来源:https://github.com/cmdparkour/vue-admin-box/tree/template-js
vuex插件介绍:https://vuex.vuejs.org/zh/guide/plugins.html
vite Glob 环境变量:https://cn.vitejs.dev/guide/features#glob-import