当页面刷新时,Vuex 的 state 数据会丢失,这是因为 Vuex 的状态存储在内存中,刷新浏览器会重置 JavaScript 的运行环境。下面我将详细介绍几种解决方案,从简单到复杂,帮助你根据项目需求选择最适合的方法。
原理:在 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
优点:
缺点:
通过 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 设计的持久化插件:
npm install vuex-persistedstate
# 或
yarn add vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
// ...
plugins: [
createPersistedState()
]
})
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 的容量限制(通常 5MB)可能不够,可以使用 IndexedDB:
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'
})
]
})
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')
})
}
}
}
对于需要长期保存的用户数据,应该同步到服务器:
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)
}
}
}
在 App.vue 或根组件中:
export default {
created() {
// 从服务器加载初始数据
this.$store.dispatch('loadInitialData')
}
}
一个完整的持久化策略通常包含以下层次:
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]
})
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 中使用 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 | 大数据量、复杂应用 | 存储容量大 | 实现较复杂 |
服务端同步 | 关键用户数据 | 数据安全可靠 | 需要网络请求 |
最佳实践建议:
vuex-persistedstate
+ localStorage