Vue3 状态管理 (Pinia) 详解
概述
Pinia 是 Vue3 推荐的状态管理库,它比 Vuex 更轻量、更类型安全。状态管理就像是应用的"大脑",集中管理所有组件需要共享的数据。
状态管理架构
基本概念
1. Store
Store 是 Pinia 的核心概念,它是一个包含状态和业务逻辑的实体。
// stores/counter.js
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
// 状态
state: () => ({
count: 0,
name: "计数器",
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne: (state) => state.count * 2 + 1,
},
// 方法
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
incrementBy(amount) {
this.count += amount;
},
},
});
2. 在组件中使用 Store
<template>
<div>
<h2>{{ counter.name }}</h2>
<p>当前计数: {{ counter.count }}</p>
<p>双倍计数: {{ counter.doubleCount }}</p>
<p>双倍计数加一: {{ counter.doubleCountPlusOne }}</p>
<button @click="counter.increment">增加</button>
<button @click="counter.decrement">减少</button>
<button @click="counter.incrementBy(5)">增加5</button>
</div>
</template>
<script>
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
return {
counter
}
}
}
</script>
Store 结构详解
1. State
State 是 Store 的数据源,类似于组件的 data。
export const useUserStore = defineStore("user", {
state: () => ({
// 基本类型
name: "",
age: 0,
isLoggedIn: false,
// 对象类型
profile: {
avatar: "",
email: "",
phone: "",
},
// 数组类型
permissions: [],
// 复杂对象
settings: {
theme: "light",
language: "zh-CN",
notifications: {
email: true,
push: false,
sms: true,
},
},
}),
});
2. Getters
Getters 类似于计算属性,基于 state 派生数据。
export const useUserStore = defineStore("user", {
state: () => ({
users: [
{ id: 1, name: "张三", age: 25, role: "admin" },
{ id: 2, name: "李四", age: 30, role: "user" },
{ id: 3, name: "王五", age: 28, role: "user" },
],
}),
getters: {
// 基本 getter
totalUsers: (state) => state.users.length,
// 带参数的 getter
getUserById: (state) => (id) => {
return state.users.find((user) => user.id === id);
},
// 使用其他 getter
adminUsers: (state) => state.users.filter((user) => user.role === "admin"),
adminCount: (state, getters) => getters.adminUsers.length,
// 复杂计算
userStats: (state) => {
const total = state.users.length;
const admins = state.users.filter((user) => user.role === "admin").length;
const users = total - admins;
const avgAge =
state.users.reduce((sum, user) => sum + user.age, 0) / total;
return {
total,
admins,
users,
avgAge: Math.round(avgAge),
};
},
},
});
3. Actions
Actions 用于处理异步操作和业务逻辑。
export const useUserStore = defineStore("user", {
state: () => ({
users: [],
loading: false,
error: null,
}),
actions: {
// 异步获取用户列表
async fetchUsers() {
this.loading = true;
this.error = null;
try {
const response = await fetch("/api/users");
const data = await response.json();
this.users = data;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
},
// 添加用户
async addUser(userData) {
try {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
});
const newUser = await response.json();
this.users.push(newUser);
return newUser;
} catch (error) {
this.error = error.message;
throw error;
}
},
// 更新用户
async updateUser(id, updates) {
try {
const response = await fetch(`/api/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updates),
});
const updatedUser = await response.json();
const index = this.users.findIndex((user) => user.id === id);
if (index !== -1) {
this.users[index] = updatedUser;
}
return updatedUser;
} catch (error) {
this.error = error.message;
throw error;
}
},
// 删除用户
async deleteUser(id) {
try {
await fetch(`/api/users/${id}`, {
method: "DELETE",
});
const index = this.users.findIndex((user) => user.id === id);
if (index !== -1) {
this.users.splice(index, 1);
}
} catch (error) {
this.error = error.message;
throw error;
}
},
// 重置状态
resetState() {
this.users = [];
this.loading = false;
this.error = null;
},
},
});
组合式 API 风格
Pinia 也支持组合式 API 的写法,使用 defineStore 的第二个参数为函数。
export const useCounterStore = defineStore("counter", () => {
// 状态
const count = ref(0);
const name = ref("计数器");
// 计算属性
const doubleCount = computed(() => count.value * 2);
const doubleCountPlusOne = computed(() => count.value * 2 + 1);
// 方法
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function incrementBy(amount) {
count.value += amount;
}
return {
count,
name,
doubleCount,
doubleCountPlusOne,
increment,
decrement,
incrementBy,
};
});
Store 之间的交互
1. 在 Store 中使用其他 Store
export const useCartStore = defineStore("cart", {
state: () => ({
items: [],
}),
getters: {
totalItems: (state) => state.items.length,
totalPrice: (state) => {
return state.items.reduce(
(total, item) => total + item.price * item.quantity,
0
);
},
},
actions: {
addItem(product, quantity = 1) {
// 使用用户 store 检查登录状态
const userStore = useUserStore();
if (!userStore.isLoggedIn) {
throw new Error("请先登录");
}
const existingItem = this.items.find((item) => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({
...product,
quantity,
});
}
},
},
});
2. 组合多个 Store
export const useAppStore = defineStore("app", () => {
const userStore = useUserStore();
const cartStore = useCartStore();
const counterStore = useCounterStore();
// 应用级别的状态
const theme = ref("light");
const language = ref("zh-CN");
// 应用级别的计算属性
const isReady = computed(() => {
return userStore.isLoggedIn && !cartStore.loading;
});
// 应用级别的方法
function resetAllStores() {
userStore.resetState();
cartStore.resetState();
counterStore.resetState();
theme.value = "light";
language.value = "zh-CN";
}
return {
theme,
language,
isReady,
resetAllStores,
};
});
持久化存储
1. 使用 pinia-plugin-persistedstate
// main.js
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
export const useUserStore = defineStore("user", {
state: () => ({
token: "",
userInfo: null,
}),
persist: {
// 启用持久化
enabled: true,
// 指定要持久化的字段
paths: ["token", "userInfo"],
// 存储策略
strategies: [
{
key: "user",
storage: localStorage,
paths: ["token", "userInfo"],
},
{
key: "user-session",
storage: sessionStorage,
paths: ["token"],
},
],
},
});
2. 手动实现持久化
export const useSettingsStore = defineStore("settings", {
state: () => ({
theme: "light",
language: "zh-CN",
fontSize: 14,
}),
actions: {
// 保存到本地存储
saveToStorage() {
localStorage.setItem(
"settings",
JSON.stringify({
theme: this.theme,
language: this.language,
fontSize: this.fontSize,
})
);
},
// 从本地存储加载
loadFromStorage() {
const saved = localStorage.getItem("settings");
if (saved) {
const settings = JSON.parse(saved);
this.theme = settings.theme || "light";
this.language = settings.language || "zh-CN";
this.fontSize = settings.fontSize || 14;
}
},
// 更新设置并保存
updateSettings(updates) {
Object.assign(this, updates);
this.saveToStorage();
},
},
});
开发工具支持
Pinia 提供了优秀的开发工具支持,可以在 Vue DevTools 中查看和调试 Store。
// 在开发环境中启用
if (process.env.NODE_ENV === "development") {
const { devtools } = await import("pinia/devtools");
devtools();
}
最佳实践
1. Store 设计原则
- 按功能模块划分 Store
- 保持 Store 的单一职责
- 避免 Store 之间的循环依赖
2. 状态管理
- 将共享状态放在 Store 中
- 将组件内部状态保留在组件中
- 合理使用 computed 和 watch
3. 异步操作
- 在 actions 中处理异步操作
- 使用 try-catch 处理错误
- 提供加载状态和错误状态
4. 性能优化
export const useUserStore = defineStore("user", {
state: () => ({
users: [],
userMap: new Map(), // 使用 Map 提高查找性能
}),
getters: {
getUserById: (state) => (id) => {
return (
state.userMap.get(id) || state.users.find((user) => user.id === id)
);
},
},
actions: {
setUsers(users) {
this.users = users;
// 构建 Map 索引
this.userMap.clear();
users.forEach((user) => this.userMap.set(user.id, user));
},
},
});
总结
Pinia 为 Vue3 提供了现代化、类型安全的状态管理解决方案。通过合理设计 Store 结构、使用 Getters 和 Actions、实现持久化存储等,可以构建出高效、可维护的状态管理系统。掌握这些概念和最佳实践,将帮助你构建出更好的 Vue 应用。
