解决页面刷新后 Vuex 状态丢失的完整方案

当页面刷新时,Vuex 的 state 数据会丢失,这是因为 Vuex 的状态存储在内存中,刷新浏览器会重置 JavaScript 的运行环境。下面我将详细介绍几种解决方案,从简单到复杂,帮助你根据项目需求选择最适合的方法。

一、使用浏览器本地存储(localStorage/sessionStorage)

1.1 基础实现方案

原理:在 state 变化时将数据存入 localStorage,初始化时读取

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    
    
  state: {
    
    
    // 从 localStorage 初始化状态
    user: JSON.parse(localStorage.getItem('user') || null,
    settings: JSON.parse(localStorage.getItem('settings')) || {
    
    }
  },
  mutations: {
    
    
    setUser(state, user) {
    
    
      state.user = user
      // 状态变化时保存到 localStorage
      localStorage.setItem('user', JSON.stringify(user))
    },
    updateSettings(state, settings) {
    
    
      state.settings = settings
      localStorage.setItem('settings', JSON.stringify(settings))
    }
  }
})

export default store

优点

  • 实现简单直接
  • 不需要额外依赖

缺点

  • 需要手动管理每个状态的持久化
  • 代码重复度高

1.2 自动持久化方案

通过 Vuex 的插件机制自动保存所有状态:

const localStoragePlugin = store => {
    
    
  // 初始化时从 localStorage 恢复状态
  if (localStorage.getItem('vuex')) {
    
    
    store.replaceState(
      Object.assign({
    
    }, store.state, JSON.parse(localStorage.getItem('vuex')))
    )
  }
  
  // 订阅 store 变化
  store.subscribe((mutation, state) => {
    
    
    // 每次 mutation 后保存整个状态
    localStorage.setItem('vuex', JSON.stringify(state))
  })
}

const store = new Vuex.Store({
    
    
  // ...state, mutations, actions 等
  plugins: [localStoragePlugin]
})

优化点

  • 可以只持久化特定模块的状态
  • 添加防抖避免频繁写入
const persistState = debounce((state) => {
    
    
  const persistData = {
    
    
    auth: state.auth,  // 只持久化 auth 模块
    user: state.user   // 和 user 状态
  }
  localStorage.setItem('vuex', JSON.stringify(persistData))
}, 1000)

二、使用 vuex-persistedstate 插件

2.1 基本使用

这是一个专门为 Vuex 设计的持久化插件:

npm install vuex-persistedstate
# 或
yarn add vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
    
    
  // ...
  plugins: [
    createPersistedState()
  ]
})

2.2 高级配置

createPersistedState({
    
    
  key: 'my-vuex-storage',       // 存储键名,默认是 'vuex'
  storage: window.sessionStorage, // 可替换为 sessionStorage
  paths: [                     // 指定要持久化的状态路径
    'user',
    'settings.theme'
  ],
  reducer: (state) => {
    
            // 自定义过滤函数
    return {
    
    
      auth: state.auth,
      project: state.project.currentProject
    }
  }
})

插件特点

  • 支持多种存储方式(localStorage, sessionStorage, cookies)
  • 可以指定持久化的状态路径
  • 支持自定义序列化方法
  • 默认使用防抖优化性能

三、使用 IndexedDB 存储大量数据

当需要存储大量数据时,localStorage 的容量限制(通常 5MB)可能不够,可以使用 IndexedDB:

扫描二维码关注公众号,回复: 17586986 查看本文章

3.1 使用 localForage 库

npm install localforage
import localforage from 'localforage'
import {
    
     extendPrototype } from 'localforage-vuex'

// 扩展 Vuex.Store
extendPrototype(Vuex.Store)

const store = new Vuex.Store({
    
    
  // ...
  plugins: [
    localforage.createStore({
    
    
      driver: localforage.INDEXEDDB,
      name: 'my-app',
      storeName: 'vuex_persist'
    })
  ]
})

3.2 自定义 IndexedDB 实现

function indexedDBPlugin() {
    
    
  return store => {
    
    
    const request = indexedDB.open('vuex-store', 1)
    
    request.onupgradeneeded = event => {
    
    
      const db = event.target.result
      if (!db.objectStoreNames.contains('state')) {
    
    
        db.createObjectStore('state')
      }
    }
    
    request.onsuccess = event => {
    
    
      const db = event.target.result
      const transaction = db.transaction('state', 'readonly')
      const objectStore = transaction.objectStore('state')
      const getRequest = objectStore.get('state')
      
      getRequest.onsuccess = () => {
    
    
        if (getRequest.result) {
    
    
          store.replaceState(getRequest.result)
        }
      }
      
      store.subscribe((mutation, state) => {
    
    
        const putTransaction = db.transaction('state', 'readwrite')
        putTransaction.objectStore('state').put(state, 'state')
      })
    }
  }
}

四、服务端持久化方案

对于需要长期保存的用户数据,应该同步到服务器:

4.1 在 actions 中实现同步

actions: {
    
    
  updateProfile({
     
      commit }, profile) {
    
    
    return api.updateProfile(profile)
      .then(updatedProfile => {
    
    
        commit('SET_PROFILE', updatedProfile)
        return updatedProfile
      })
  },
  async loadInitialData({
     
      commit }) {
    
    
    try {
    
    
      const [user, settings] = await Promise.all([
        api.getUser(),
        api.getSettings()
      ])
      commit('SET_USER', user)
      commit('SET_SETTINGS', settings)
    } catch (error) {
    
    
      console.error('Failed to load initial data:', error)
    }
  }
}

4.2 页面加载时初始化

在 App.vue 或根组件中:

export default {
    
    
  created() {
    
    
    // 从服务器加载初始数据
    this.$store.dispatch('loadInitialData')
  }
}

五、综合解决方案

一个完整的持久化策略通常包含以下层次:

  1. 短期存储:使用 sessionStorage 保存会话期间的状态
  2. 长期存储:使用 localStorage 或 IndexedDB 保存用户偏好
  3. 关键数据:同步到服务器确保数据安全
import createPersistedState from 'vuex-persistedstate'
import localforage from 'localforage'

// 不同存储策略
const sessionPersist = createPersistedState({
    
    
  storage: window.sessionStorage,
  paths: ['auth.token']  // 会话 token 使用 sessionStorage
})

const localPersist = createPersistedState({
    
    
  paths: ['user.preferences']  // 用户偏好使用 localStorage
})

const dbPersist = {
    
    
  async getItem(key) {
    
    
    return (await localforage.getItem(key)) || null
  },
  async setItem(key, value) {
    
    
    await localforage.setItem(key, value)
  },
  async removeItem(key) {
    
    
    await localforage.removeItem(key)
  }
}

const foragePersist = createPersistedState({
    
    
  storage: dbPersist,
  paths: ['projects']  // 大型数据使用 IndexedDB
})

const store = new Vuex.Store({
    
    
  // ...
  plugins: [sessionPersist, localPersist, foragePersist]
})

六、安全注意事项

  1. 敏感信息:不要存储敏感数据(如密码、token)在客户端
  2. 加密存储:对重要数据进行加密
  3. 数据验证:从存储加载时要验证数据格式
  4. 存储限制:注意 localStorage 的大小限制(通常 5MB)
import CryptoJS from 'crypto-js'

const SECRET_KEY = 'your-secret-key'

const secureStorage = {
    
    
  getItem(key) {
    
    
    const encrypted = localStorage.getItem(key)
    if (!encrypted) return null
    const bytes = CryptoJS.AES.decrypt(encrypted, SECRET_KEY)
    return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
  },
  setItem(key, value) {
    
    
    const encrypted = CryptoJS.AES.encrypt(
      JSON.stringify(value), 
      SECRET_KEY
    ).toString()
    localStorage.setItem(key, encrypted)
  }
}

七、Nuxt.js 中的特殊处理

在 Nuxt.js 中使用 Vuex 时,由于服务端渲染的特性,需要特别注意:

// store/index.js
export const actions = {
    
    
  nuxtServerInit({
     
      commit }, {
     
      req }) {
    
    
    // 从 cookie 初始化状态
    if (req.headers.cookie) {
    
    
      const cookies = cookie.parse(req.headers.cookie)
      if (cookies.token) {
    
    
        commit('auth/SET_TOKEN', cookies.token)
      }
    }
  }
}

配合 js-cookie 在客户端管理:

import Cookies from 'js-cookie'

const cookiePlugin = store => {
    
    
  store.subscribe((mutation, state) => {
    
    
    if (mutation.type === 'auth/SET_TOKEN') {
    
    
      Cookies.set('token', state.auth.token, {
    
     expires: 7 })
    }
  })
}

总结

根据项目需求,可以选择以下方案:

方案 适用场景 优点 缺点
localStorage 简单应用、小数据量 简单易用 有大小限制、不安全
vuex-persistedstate 大多数 Vuex 项目 配置灵活、功能完善 需要额外依赖
IndexedDB/localForage 大数据量、复杂应用 存储容量大 实现较复杂
服务端同步 关键用户数据 数据安全可靠 需要网络请求

最佳实践建议

  1. 小型应用使用 vuex-persistedstate + localStorage
  2. 中大型应用组合使用 sessionStorage(会话数据)、IndexedDB(应用数据)和服务端存储(用户数据)
  3. 敏感信息应该加密存储或避免存储在客户端
  4. 注意清理过期的存储数据,避免占用过多空间

通过合理组合这些技术,可以有效解决 Vuex 状态在页面刷新后丢失的问题,同时保证应用的性能和安全性。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_16242613/article/details/147021426
今日推荐