各位观众老爷们,大家好!今天咱来聊聊 Vuex 里 State 那些事儿,特别是怎么让它“持久”一点,别一刷新就全没了。
开场白:State 的“短暂人生”
Vuex 的 State 就像我们程序里的“记忆”,用来存储应用的数据。但是,这“记忆”有个毛病,就是太短暂了!页面一刷新,或者浏览器一关,State 里的数据就灰飞烟灭了。这在很多场景下可不行,比如用户登录信息、购物车数据、上次浏览的商品等等,都得记住才行啊。
那咋办呢?我们需要给 State 找个“硬盘”,让它把数据存起来,下次启动的时候再读回来。这个“硬盘”就是浏览器提供的存储机制,比如 localStorage
和 sessionStorage
。
第一章:localStorage
和 sessionStorage
:两位好基友
这两位都是浏览器提供的存储 API,用来在客户端存储数据。它们的主要区别在于数据的生命周期:
localStorage
: 永久存储,除非用户手动清除,否则数据会一直存在。适合存储用户设置、登录信息等长期保存的数据。sessionStorage
: 会话存储,当浏览器窗口关闭时,数据会被清除。适合存储临时数据,比如购物车信息,避免用户下次打开时看到过期数据。
咱们可以用表格来总结一下:
特性 | localStorage |
sessionStorage |
---|---|---|
生命周期 | 永久 | 会话 |
数据共享 | 同域共享 | 同窗口共享 |
使用场景 | 用户设置、登录信息 | 购物车信息、临时数据 |
第二章:手动实现 State 持久化:原始人的智慧
最简单粗暴的方法,就是每次 State 改变的时候,手动把数据存到 localStorage
或 sessionStorage
里,然后在 Vuex 初始化的时候,从存储里读取数据。
2.1 初始化 State
首先,在你的 Vuex store.js
文件里,定义一个函数来初始化 State:
// store.js
function initialState() {
try {
// 尝试从 localStorage 中读取数据
const storedState = localStorage.getItem('my-app-state');
if (storedState) {
return JSON.parse(storedState);
}
} catch (e) {
// 如果读取或解析失败,返回默认的 state
console.error('Failed to load state from localStorage:', e);
}
// 默认的 state
return {
user: null,
cart: [],
settings: {
theme: 'light',
language: 'en'
}
};
}
export default new Vuex.Store({
state: initialState(),
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
}
});
这段代码首先尝试从 localStorage
中读取名为 my-app-state
的数据,如果存在,就用 JSON.parse
解析成 JavaScript 对象,作为 State 的初始值。如果读取失败或者 localStorage
里没有数据,就使用默认的 State。
2.2 监听 State 变化并保存
接下来,我们需要在每次 State 发生变化时,把新的 State 保存到 localStorage
里。我们可以使用 Vuex 的 subscribe
方法来实现:
// store.js
const store = new Vuex.Store({
state: initialState(),
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
}
});
// 监听 state 的变化
store.subscribe((mutation, state) => {
try {
localStorage.setItem('my-app-state', JSON.stringify(state));
} catch (e) {
console.error('Failed to save state to localStorage:', e);
}
});
export default store;
store.subscribe
接收一个回调函数,这个回调函数会在每次 mutation 完成后被调用。回调函数接收两个参数:mutation
和 state
。我们只需要把当前的 state
用 JSON.stringify
转换成字符串,然后保存到 localStorage
里就可以了。
2.3 简单示例
现在我们来个简单的例子:
<template>
<div>
<p>用户名: {{ user ? user.name : '未登录' }}</p>
<button @click="login">登录</button>
<p>当前主题: {{ theme }}</p>
<button @click="toggleTheme">切换主题</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState(['user', 'settings']),
theme() {
return this.settings.theme;
}
},
methods: {
...mapActions(['login', 'changeTheme']),
login() {
const mockUser = { id: 1, name: '张三' };
this.login(mockUser);
},
toggleTheme() {
const newTheme = this.theme === 'light' ? 'dark' : 'light';
this.changeTheme(newTheme);
}
}
};
</script>
在这个例子里,我们有两个按钮:一个用来模拟登录,另一个用来切换主题。每次点击按钮,都会触发对应的 action,然后更新 State,最后 State 的变化会被 store.subscribe
监听到,并保存到 localStorage
里。
刷新页面,你会发现用户名和主题都保持不变,因为它们已经被持久化了。
第三章:Vuex Plugins:优雅的解决方案
手动实现 State 持久化虽然简单,但是比较繁琐,每次都要写 localStorage.setItem
和 JSON.parse
。而且,如果你的 State 结构比较复杂,手动处理起来就更麻烦了。
这时候,Vuex Plugins 就派上用场了。Vuex Plugins 允许我们扩展 Vuex 的功能,比如我们可以写一个 Plugin 来自动处理 State 的持久化。
3.1 创建一个 Vuex Plugin
首先,创建一个名为 vuex-persist.js
的文件:
// vuex-persist.js
export default function createPersistedState(options = {}) {
const {
key = 'vuex', // localStorage 的 key
storage = localStorage, // 使用的 storage 对象,默认为 localStorage
reducer = state => state, // 用于转换 state 的函数,默认为 identity function
rehydrated = () => {} // state 加载完成后的回调函数
} = options;
return store => {
// 初始化时,从 storage 中读取 state
try {
const storedState = storage.getItem(key);
if (storedState) {
store.replaceState(JSON.parse(storedState));
}
} catch (e) {
console.error('Failed to load state from storage:', e);
} finally {
rehydrated();
}
// 监听 mutations,保存 state 到 storage
store.subscribe((mutation, state) => {
try {
storage.setItem(key, JSON.stringify(reducer(state)));
} catch (e) {
console.error('Failed to save state to storage:', e);
}
});
};
}
这个 Plugin 接收一个 options
对象,可以用来配置 localStorage
的 key、使用的 storage 对象、以及一些回调函数。
Plugin 的主要逻辑是:
- 在初始化时,从
storage
中读取 State,并使用store.replaceState
替换 Vuex 的 State。 - 监听 mutations,每次 mutation 完成后,把新的 State 保存到
storage
里。
3.2 使用 Vuex Plugin
在你的 store.js
文件里,引入并使用这个 Plugin:
// store.js
import Vuex from 'vuex';
import createPersistedState from './vuex-persist';
export default new Vuex.Store({
state: {
user: null,
cart: [],
settings: {
theme: 'light',
language: 'en'
}
},
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
},
plugins: [
createPersistedState({
key: 'my-app-state', // localStorage 的 key
storage: localStorage // 使用 localStorage
})
]
});
只需要在 plugins
数组里添加 createPersistedState
,并传入一些配置项,就可以自动处理 State 的持久化了。
3.3 进阶用法:Partial Persistence
有时候,我们只需要持久化 State 的一部分,而不是全部。比如,我们只想持久化 user
和 settings
,而不想持久化 cart
。
这时候,我们可以使用 reducer
函数来过滤 State:
// store.js
import Vuex from 'vuex';
import createPersistedState from './vuex-persist';
export default new Vuex.Store({
state: {
user: null,
cart: [],
settings: {
theme: 'light',
language: 'en'
}
},
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
},
plugins: [
createPersistedState({
key: 'my-app-state', // localStorage 的 key
storage: localStorage, // 使用 localStorage
reducer: (state) => { // 只保留 user 和 settings
return {
user: state.user,
settings: state.settings
};
}
})
]
});
在 reducer
函数里,我们只返回了 user
和 settings
,这样只有这两个属性会被持久化。
第四章:第三方库:站在巨人的肩膀上
除了自己写 Plugin,我们还可以使用一些现成的第三方库,它们提供了更丰富的功能和更灵活的配置。
4.1 vuex-persistedstate
vuex-persistedstate
是一个非常流行的 Vuex State 持久化库,它支持多种 storage 对象(localStorage
、sessionStorage
、Cookie 等),以及多种配置选项。
安装:
npm install vuex-persistedstate
使用:
// store.js
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
export default new Vuex.Store({
state: {
user: null,
cart: [],
settings: {
theme: 'light',
language: 'en'
}
},
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
},
plugins: [
createPersistedState({
key: 'my-app-state',
storage: window.localStorage,
reducer(state) {
return {
user: state.user,
settings: state.settings
};
}
})
]
});
vuex-persistedstate
提供了很多配置选项,比如:
key
:localStorage 的 key。storage
:使用的 storage 对象,默认为localStorage
。paths
:一个数组,指定要持久化的 State 路径。reducer
:一个函数,用于转换 State。subscriber
:一个函数,用于自定义 State 的保存逻辑。
4.2 vuex-persist
(另一个库)
注意,这里还有一个名为 vuex-persist
的库,但它跟我们前面自己写的 Plugin 同名,容易混淆。这个库也提供 State 持久化功能,但 API 和用法略有不同。
安装:
npm install vuex-persist
使用:
// store.js
import Vuex from 'vuex';
import VuexPersist from 'vuex-persist';
const vuexPersist = new VuexPersist({
key: 'my-app-state',
storage: window.localStorage,
reducer: state => ({
user: state.user,
settings: state.settings
})
});
export default new Vuex.Store({
state: {
user: null,
cart: [],
settings: {
theme: 'light',
language: 'en'
}
},
mutations: {
setUser(state, user) {
state.user = user;
},
addToCart(state, item) {
state.cart.push(item);
},
setTheme(state, theme) {
state.settings.theme = theme;
}
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
addItemToCart({ commit }, item) {
commit('addToCart', item);
},
changeTheme({ commit }, theme) {
commit('setTheme', theme);
}
},
plugins: [
vuexPersist.plugin
]
});
这个库需要先创建一个 VuexPersist
实例,然后把它的 plugin
属性添加到 Vuex 的 plugins
数组里。
第五章:注意事项:别踩坑里了!
- 数据大小限制:
localStorage
和sessionStorage
都有数据大小限制,一般是 5MB 左右。如果你的 State 非常大,可能会超出限制,导致数据无法保存。这时候,可以考虑只持久化 State 的一部分,或者使用 IndexedDB 等更大的存储方案。 - 数据类型:
localStorage
和sessionStorage
只能存储字符串。所以,你需要使用JSON.stringify
把 JavaScript 对象转换成字符串,然后再保存。读取的时候,需要使用JSON.parse
把字符串转换回 JavaScript 对象。 - 数据安全性:
localStorage
和sessionStorage
存储的数据是明文的,任何人都可以读取。所以,不要存储敏感数据,比如用户密码、银行卡号等。如果需要存储敏感数据,应该进行加密处理。 - 异步操作: 如果你的 State 里有异步操作的结果,比如 Promise,那么在持久化的时候可能会出现问题。因为 Promise 在序列化成 JSON 字符串时,会丢失其状态。所以,在持久化之前,需要把 Promise 转换成普通的值,比如使用
await
等待 Promise 完成。 - 兼容性: 某些浏览器可能不支持
localStorage
或sessionStorage
。在使用之前,最好先检测一下浏览器是否支持这些 API。
总结:选择适合你的方案
今天我们聊了 Vuex State 的持久化方案,从最原始的手动实现,到使用 Vuex Plugins,再到使用第三方库,每种方案都有其优缺点。
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
手动实现 | 简单易懂,灵活可控 | 繁琐,容易出错,代码冗余 | 简单的项目,或者只需要持久化少量数据 |
Vuex Plugin | 优雅,可复用,可定制 | 需要自己编写 Plugin,有一定的学习成本 | 中小型项目,需要持久化部分 State |
第三方库 | 功能丰富,配置灵活,社区支持良好 | 引入第三方依赖,可能会增加项目体积 | 大型项目,需要持久化大量数据,或者需要更高级的功能 |
选择哪种方案,取决于你的项目规模、需求复杂度和个人偏好。希望今天的讲座能对你有所帮助。
各位观众老爷们,下次再见!