🎯 什么是Pinia?
Pinia是Vue 3的官方状态管理库,也是Vuex的继任者。它提供了更简洁、更直观的API,完全支持TypeScript,并且具有出色的开发体验。
与其他状态管理工具对比:
React: Redux
Vue 2: Vuex
Vue 3: Pinia(推荐)
🚀 Pinia的优势
✅ 更简洁的API,无需mutations
✅ 完整的TypeScript支持
✅ 模块化设计,自动代码分割
✅ 支持Vue DevTools调试
✅ 没有魔法字符串,更安全
✅ 支持组合式API和选项式API
📦 安装与配置
1. 安装Pinia
npm install pinia
# 或
yarn add pinia
# 或
pnpm add piniaVue 3 项目配置
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
// 创建Pinia实例
const pinia = createPinia()
// 创建Vue应用实例
const app = createApp(App)
// 注册Pinia插件
app.use(pinia)
// 挂载应用
app.mount('#app')Vue 2 项目配置(兼容模式)
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
// 注册插件
Vue.use(PiniaVuePlugin)
// 创建Pinia实例
const pinia = createPinia()
// 创建Vue实例
new Vue({
el: '#app',
pinia,
// 其他配置...
})🏗️ 创建Store
1. 项目结构
src/
├── stores/ # 存放所有store
│ ├── token.js # token相关状态
│ ├── user.js # 用户信息状态
│ └── teacher.js # 教师数据状态
├── views/
└── components/
2. 选项式API定义Store
// stores/teacher.js
import { defineStore } from "pinia";
export const useTeacherStore = defineStore('teacher', {
// state: 存储数据的函数
state: () => ({
teachers: [],
currentTeacher: null,
loading: false
}),
// getters: 计算属性,类似computed
getters: {
teacherCount: (state) => state.teachers.length,
activeTeachers: (state) => state.teachers.filter(t => t.isActive),
// 可以使用this访问其他getters
formattedTeachers: (state) => {
return state.teachers.map(teacher => ({
...teacher,
fullName: `${teacher.firstName} ${teacher.lastName}`
}))
}
},
// actions: 修改状态的方法,支持异步
actions: {
async fetchTeachers() {
this.loading = true;
try {
const response = await api.getTeachers();
this.teachers = response.data;
} catch (error) {
console.error('获取教师列表失败:', error);
} finally {
this.loading = false;
}
},
addTeacher(teacher) {
this.teachers.push(teacher);
},
updateTeacher(id, updates) {
const index = this.teachers.findIndex(t => t.id === id);
if (index !== -1) {
this.teachers[index] = { ...this.teachers[index], ...updates };
}
}
}
});3. 组合式API定义Store
// stores/student.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { getStudents } from "@/api/student";
export const useStudentStore = defineStore('student', () => {
// state
const students = ref([]);
const loading = ref(false);
const searchQuery = ref('');
// getters
const studentCount = computed(() => students.value.length);
const filteredStudents = computed(() => {
return students.value.filter(student =>
student.name.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
// actions
const fetchStudents = async () => {
loading.value = true;
try {
const response = await getStudents();
students.value = response.data;
} catch (error) {
console.error('获取学生列表失败:', error);
} finally {
loading.value = false;
}
};
const addStudent = (student) => {
students.value.push(student);
};
const setSearchQuery = (query) => {
searchQuery.value = query;
};
return {
// state
students,
loading,
searchQuery,
// getters
studentCount,
filteredStudents,
// actions
fetchStudents,
addStudent,
setSearchQuery
};
});🔧 在组件中使用Store
1. 基本使用
import { useTeacherStore } from "@/stores/teacher.js";
export default {
setup() {
const teacherStore = useTeacherStore();
// 访问state
console.log(teacherStore.teachers);
// 访问getters
console.log(teacherStore.teacherCount);
// 调用actions
teacherStore.fetchTeachers();
return {
teacherStore
};
}
}2. 数据修改的三种方法
import { useTeacherStore } from "@/stores/teacher.js";
const teacherStore = useTeacherStore();
// 方法1: 直接修改(简单场景)
teacherStore.teachers = [];
// 方法2: 通过action修改(推荐)
teacherStore.addTeacher({ name: '张老师', subject: '数学' });
// 方法3: 使用$patch批量修改
teacherStore.$patch({
teachers: [],
loading: false
});
// 方法4: $patch函数形式(支持逻辑)
teacherStore.$patch((state) => {
state.teachers.push({ name: '李老师' });
state.loading = true;
});3. 使用storeToRefs简化访问
import { storeToRefs } from "pinia";
import { useTeacherStore } from "@/stores/teacher.js";
export default {
setup() {
const teacherStore = useTeacherStore();
// ❌ 不推荐:直接访问会失去响应性
// const { teachers, loading } = teacherStore;
// ✅ 推荐:使用storeToRefs保持响应性
const { teachers, loading, teacherCount } = storeToRefs(teacherStore);
// actions不需要解构,直接使用
const { fetchTeachers, addTeacher } = teacherStore;
return {
teachers,
loading,
teacherCount,
fetchTeachers,
addTeacher
};
}
}🔐 Token管理实例
1. Token Store定义
// stores/token.js
import { defineStore } from "pinia";
export const useTokenStore = defineStore("token", {
state: () => ({
token: localStorage.getItem("token") || "",
refreshToken: localStorage.getItem("refreshToken") || "",
userInfo: JSON.parse(localStorage.getItem("userInfo") || "{}")
}),
getters: {
isAuthenticated: (state) => !!state.token,
isAdmin: (state) => state.userInfo.role === 'admin',
userName: (state) => state.userInfo.name || '未登录用户'
},
actions: {
setToken(token) {
this.token = token;
localStorage.setItem("token", token);
},
setRefreshToken(refreshToken) {
this.refreshToken = refreshToken;
localStorage.setItem("refreshToken", refreshToken);
},
setUserInfo(userInfo) {
this.userInfo = userInfo;
localStorage.setItem("userInfo", JSON.stringify(userInfo));
},
// 登录
async login(credentials) {
try {
const response = await api.login(credentials);
this.setToken(response.data.token);
this.setRefreshToken(response.data.refreshToken);
this.setUserInfo(response.data.user);
return response.data;
} catch (error) {
throw error;
}
},
// 登出
logout() {
this.token = "";
this.refreshToken = "";
this.userInfo = {};
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
localStorage.removeItem("userInfo");
},
// 检查token有效性
checkToken() {
return this.token && this.token !== "null";
}
}
});2. 在组件中使用Token
import { useTokenStore } from "@/stores/token";
import { storeToRefs } from "pinia";
import { ElMessage } from "element-plus";
export default {
setup() {
const tokenStore = useTokenStore();
const { isAuthenticated, userName, isAdmin } = storeToRefs(tokenStore);
// 检查权限
function checkAdminPermission() {
if (!isAdmin.value) {
ElMessage.error("权限不足");
return false;
}
return true;
}
// 登出
function handleLogout() {
tokenStore.logout();
ElMessage.success("注销成功");
// 跳转到登录页
router.push('/login');
}
// 检查token状态
function validateToken() {
return tokenStore.checkToken();
}
return {
isAuthenticated,
userName,
isAdmin,
checkAdminPermission,
handleLogout,
validateToken
};
}
}📊 监听Store变化
1. 使用$subscribe监听
import { useTeacherStore } from "@/stores/teacher.js";
const teacherStore = useTeacherStore();
// 监听store变化
const unsubscribe = teacherStore.$subscribe((mutation, state) => {
console.log("Store发生变化:", mutation);
console.log("新状态:", state);
// 可以在这里做持久化等操作
localStorage.setItem('teacherData', JSON.stringify(state.teachers));
});
// 组件卸载时取消监听
onUnmounted(() => {
unsubscribe();
});2. 使用$onAction监听Action
const teacherStore = useTeacherStore();
// 监听action调用
const unsubscribeAction = teacherStore.$onAction(
({
name, // action名称
store, // store实例
args, // 参数
after, // action完成后的回调
onError // action出错的回调
}) => {
console.log(`正在调用action: ${name}`);
after((result) => {
console.log(`${name} 执行完成,返回:`, result);
});
onError((error) => {
console.error(`${name} 执行失败:`, error);
});
}
);💡 最佳实践
1. Store拆分原则
按功能模块拆分(用户、产品、订单等)
避免过于庞大的单个store
相关数据放在同一个store中
2. 命名规范
// ✅ 好的命名
const useUserStore = defineStore('user', {});
const useProductStore = defineStore('product', {});
// ❌ 避免的命名
const store1 = defineStore('store1', {});
const data = defineStore('data', {});3. 类型安全(TypeScript)
// stores/user.ts
interface UserInfo {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
export const useUserStore = defineStore('user', {
state: () => ({
user: null as UserInfo | null,
loading: false as boolean
}),
getters: {
userName: (state): string => state.user?.name || '未登录',
isAdmin: (state): boolean => state.user?.role === 'admin'
},
actions: {
async setUser(user: UserInfo): Promise<void> {
this.user = user;
localStorage.setItem('user', JSON.stringify(user));
}
}
});4. 性能优化
合理使用
storeToRefs避免不必要的响应式大型数据使用computed进行派生
及时取消订阅避免内存泄漏
📝 总结
Pinia作为Vue 3的官方状态管理方案,提供了:
🔧 简洁的API: 无需mutations,更直观
📝 类型安全: 原生TypeScript支持
🏗️ 模块化: 自动代码分割,按需加载
🔄 响应性: 完美集成Vue 3响应式系统
🛠️ 开发体验: 优秀的DevTools支持
通过合理使用Pinia,可以让Vue应用的状态管理变得简单、高效、可维护!