pinia状态管理——跨组件数据共享)
目录前言一、为什么需要状态管理1.1 组件通信的困境1.2 状态管理的本质二、Pinia 是什么2.1 Pinia vs Vuex2.2 Pinia 的核心概念三、安装与配置3.1 安装3.2 挂载到应用四、定义 Store4.1 基本结构4.2 组合式 API 风格推荐五、使用 Store5.1 在组件中使用5.2 解构 Store5.3 直接修改 State5.4 订阅 State 变化六、实战案例用户认证6.1 用户 Store6.2 登录页面6.3 布局组件显示用户信息6.4 路由守卫使用 Store七、多个 Store7.1 学生 Store7.2 在组件中使用多个 Store八、Pinia 持久化8.1 安装插件8.2 配置插件8.3 在 Store 中启用持久化九、Pinia vs 直接使用 localStorage十、总结Vue3 学习路径总结前言在vue-router单页面应用路由管理中我们掌握了如何管理多个页面。但还有一个问题不同组件之间如何共享数据比如用户信息登录后获取用户信息侧边栏显示用户名右上角显示头像个人中心页展示详细信息。这些组件都需要访问同一份数据。传统做法是用 props 一层层传递或者用 provide/inject但这些方式都不够优雅。Pinia是 Vue3 官方推荐的状态管理库专门解决跨组件数据共享问题。本文将讲解 Pinia 的核心概念Store 的定义与使用、数据的响应式更新、以及与组件的结合。一、为什么需要状态管理1.1 组件通信的困境假设有三个组件需要共享用户信息App.vue ├── Header.vue 需要用户名 ├── Sidebar.vue 需要用户权限 └── Profile.vue 需要完整用户信息方式问题props 传递需要层层传递繁琐provide/inject祖先向后代传递但数据来源不清晰localStorage需要手动同步不是响应式的1.2 状态管理的本质状态管理就是把多个组件共享的数据抽取到一个独立的地方管理┌─────────────────────────────────────┐ │ Store仓库 │ │ ┌─────────────────────────────┐ │ │ │ state: 用户信息、权限等 │ │ │ │ actions: 获取、修改数据的方法 │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘ ↑ ↑ ↑ │ │ │ Header.vue Sidebar.vue Profile.vue类比 Java类似于 Spring 的单例 Bean全局共享所有组件都可以访问。二、Pinia 是什么2.1 Pinia vs VuexPinia 是 Vue3 官方推荐的状态管理库相比 VuexVue2 时代的方案特性VuexPinia代码量多mutations、actions少只有 state、actionsTypeScript支持较弱完美支持Composition API不友好完美支持模块化需要手动分模块每个 Store 独立文件调试工具Vue DevTools 支持Vue DevTools 支持结论Vue3 项目推荐使用 Pinia。2.2 Pinia 的核心概念概念说明类比Store存储共享数据的仓库Java 的 Service 类state数据本身类的成员变量actions操作数据的方法类的方法getters计算属性类的 getter 方法注意Pinia 没有 Vuex 的 mutations直接在 actions 中修改 state 即可。三、安装与配置3.1 安装npminstallpinia3.2 挂载到应用在main.ts中import{createApp}fromvueimport{createPinia}frompiniaimportAppfrom./App.vueimportrouterfrom./routerconstappcreateApp(App)app.use(createPinia())app.use(router)app.mount(#app)四、定义 Store4.1 基本结构新建src/stores/user.tsimport{defineStore}frompinia// 第一个参数是 store 的唯一标识exportconstuseUserStoredefineStore(user,{// state: 数据state:()({name:,age:0,sex:,token:}),// getters: 计算属性getters:{isLogin:(state)!!state.token,userInfo:(state)${state.name}(${state.sex},${state.age}岁)},// actions: 方法actions:{setToken(token:string){this.tokentoken},clearToken(){this.token},asyncfetchUserInfo(){// 调用 API 获取用户信息constdataawaitrequest.get(/api/user/info)this.namedata.namethis.agedata.agethis.sexdata.sex}}})类比 Javastate类似于类的成员变量getters类似于 getter 方法actions类似于普通方法可以修改 state4.2 组合式 API 风格推荐如果习惯 Composition API可以用setup语法import{defineStore}frompiniaimport{ref,computed}fromvueexportconstuseUserStoredefineStore(user,(){// state用 ref 包装constnameref()constageref(0)consttokenref()// getters用 computedconstisLogincomputed(()!!token.value)// actions普通函数functionsetToken(newToken:string){token.valuenewToken}functionclearToken(){token.value}asyncfunctionfetchUserInfo(){constdataawaitrequest.get(/api/user/info)name.valuedata.name age.valuedata.age}// 返回需要暴露的内容return{name,age,token,isLogin,setToken,clearToken,fetchUserInfo}})两种风格对比特性Options API 风格Composition API 风格语法类似 Vuex更灵活符合 Vue3 风格复杂度简单适中推荐场景简单 Store复杂逻辑、需要组合其他 Hook五、使用 Store5.1 在组件中使用script setup langts import { useUserStore } from /stores/user const userStore useUserStore() // 读取 state console.log(userStore.name) console.log(userStore.token) // 读取 getters console.log(userStore.isLogin) // 调用 actions userStore.setToken(xxx) userStore.fetchUserInfo() /script template div p用户名{{ userStore.name }}/p p v-ifuserStore.isLogin已登录/p button clickuserStore.clearToken退出登录/button /div /template5.2 解构 Store直接解构会失去响应式需要用storeToRefsscript setup langts import { storeToRefs } from pinia import { useUserStore } from /stores/user const userStore useUserStore() // ❌ 错误失去响应式 const { name, age } userStore // ✅ 正确保持响应式 const { name, age, isLogin } storeToRefs(userStore) // actions 不需要 storeToRefs它们本身就是函数 const { setToken, fetchUserInfo } userStore /script template p用户名{{ name }}/p p年龄{{ age }}/p /template5.3 直接修改 Statescript setup langts import { useUserStore } from /stores/user const userStore useUserStore() // 方式1直接修改 userStore.name 张三 // 方式2批量修改$patch userStore.$patch({ name: 张三, age: 18 }) // 方式3$patch 函数式 userStore.$patch((state) { state.name 张三 state.age 18 }) // 方式4替换整个 state userStore.$reset() // 重置为初始值 /script方法说明store.xxx value直接修改单个属性store.$patch(object)批量修改store.$patch(function)批量修改函数式store.$reset()重置为初始值推荐简单修改直接赋值复杂修改用$patch。5.4 订阅 State 变化script setup langts import { useUserStore } from /stores/user const userStore useUserStore() // 监听 state 变化 userStore.$subscribe((mutation, state) { console.log(变化类型:, mutation.type) // direct | patch object | patch function console.log(新状态:, state) // 持久化到 localStorage localStorage.setItem(user, JSON.stringify(state)) }) /script六、实战案例用户认证6.1 用户 Storeimport{defineStore}frompiniaimport{ref,computed}fromvueimportrequestfrom/api/requestexportconstuseUserStoredefineStore(user,(){consttokenref(localStorage.getItem(token)||)constnameref()constroleref()constisLogincomputed(()!!token.value)asyncfunctionlogin(username:string,password:string){constdataawaitrequest.post(/api/login,{username,password})token.valuedata.token name.valuedata.name role.valuedata.role localStorage.setItem(token,data.token)}asyncfunctionfetchUserInfo(){if(!token.value)returnconstdataawaitrequest.get(/api/user/info)name.valuedata.name role.valuedata.role}functionlogout(){token.valuename.valuerole.valuelocalStorage.removeItem(token)}return{token,name,role,isLogin,login,logout,fetchUserInfo}})6.2 登录页面script setup langts import { ref } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { useUserStore } from /stores/user const router useRouter() const userStore useUserStore() const form ref({ username: , password: }) async function handleLogin() { try { await userStore.login(form.value.username, form.value.password) ElMessage.success(登录成功) router.push(/) } catch (error) { ElMessage.error(登录失败) } } /script template div classlogin h2登录/h2 el-form el-form-item el-input v-modelform.username placeholder用户名 / /el-form-item el-form-item el-input v-modelform.password typepassword placeholder密码 / /el-form-item el-button typeprimary clickhandleLogin登录/el-button /el-form /div /template6.3 布局组件显示用户信息script setup langts import { onMounted } from vue import { useRouter } from vue-router import { useUserStore } from /stores/user const router useRouter() const userStore useUserStore() onMounted(() { userStore.fetchUserInfo() }) function handleLogout() { userStore.logout() router.push(/login) } /script template header classheader div classlogo学生管理系统/div div classuser-info span{{ userStore.name }}/span span classrole{{ userStore.role }}/span button clickhandleLogout退出/button /div /header /template6.4 路由守卫使用 Storeimport{useUserStore}from/stores/userrouter.beforeEach((to,from,next){constuserStoreuseUserStore()if(to.meta.requiresAuth!userStore.isLogin){next(/login)}else{next()}})七、多个 Store实际项目会有多个 Store按功能划分src/stores/ ├── user.ts # 用户相关 ├── student.ts # 学生相关 └── app.ts # 应用配置主题、语言等7.1 学生 Storeimport{defineStore}frompiniaimport{ref}fromvueimportrequestfrom/api/requestexportconstuseStudentStoredefineStore(student,(){conststudentsrefStudent[]([])consttotalref(0)constloadingref(false)asyncfunctionfetchStudents(params:QueryParams){loading.valuetruetry{constdataawaitrequest.get(/api/students/page,{params})students.valuedata.list total.valuedata.total}finally{loading.valuefalse}}asyncfunctiondeleteStudent(id:number){awaitrequest.delete(/api/students/${id})// 刷新列表awaitfetchStudents({page:1,size:10})}return{students,total,loading,fetchStudents,deleteStudent}})7.2 在组件中使用多个 Storescript setup langts import { onMounted } from vue import { useUserStore } from /stores/user import { useStudentStore } from /stores/student const userStore useUserStore() const studentStore useStudentStore() onMounted(() { studentStore.fetchStudents({ page: 1, size: 10 }) }) /script template div p当前用户{{ userStore.name }}/p el-table :datastudentStore.students v-loadingstudentStore.loading !-- ... -- /el-table /div /template八、Pinia 持久化默认情况下Pinia 的数据保存在内存中刷新页面会丢失。可以通过插件实现持久化。8.1 安装插件npminstallpinia-plugin-persistedstate8.2 配置插件import{createApp}fromvueimport{createPinia}frompiniaimportpiniaPluginPersistedstatefrompinia-plugin-persistedstateimportAppfrom./App.vueconstpiniacreatePinia()pinia.use(piniaPluginPersistedstate)constappcreateApp(App)app.use(pinia)app.mount(#app)8.3 在 Store 中启用持久化exportconstuseUserStoredefineStore(user,{state:()({token:,name:}),// 启用持久化persist:true})或者自定义持久化配置exportconstuseUserStoredefineStore(user,{state:()({token:,name:,role:}),persist:{key:my-user-store,// 存储 keystorage:localStorage,// 存储方式localStorage 或 sessionStoragepaths:[token,name]// 只持久化部分字段}})九、Pinia vs 直接使用 localStorage维度PinialocalStorage响应式✅ 自动更新❌ 需要手动监听类型安全✅ TypeScript 支持❌ 纯字符串调试✅ Vue DevTools 支持❌ 不支持持久化需要插件✅ 天然支持适用场景运行时状态长期存储推荐运行时状态用 Pinia需要持久化时配合插件使用。十、总结本文系统讲解了 Pinia 状态管理概念作用关键点Store管理共享数据defineStore定义state数据本身用ref包装Composition APIgetters计算属性用computedactions操作方法普通函数可修改 statestoreToRefs解构保持响应式只用于 state 和 getters最佳实践每个 Store 独立文件按功能划分复杂 Store 使用 Composition API 风格需要持久化时使用pinia-plugin-persistedstate解构 Store 时用storeToRefs保持响应式Vue3 学习路径总结通过这五篇博客我们已经系统掌握了 Vue3 的核心知识序号博客核心内容1Vue3入门响应式数据与数据绑定环境搭建、ref/reactive、数据绑定、计算属性、条件/列表渲染2与后端交互axios与请求封装axios 基础、拦截器、环境变量、vue-request3Element Plus实战快速构建增删改查表格、分页、表单、校验、子组件通信4vue-router单页面应用路由管理路由配置、嵌套路由、路由守卫5pinia状态管理跨组件数据共享Store 定义与使用、持久化下一步学习建议TypeScript 进阶泛型约束、类型体操、工具类型Vue3 进阶自定义 Hook、指令、插件工程化Vite 配置、打包优化、CI/CD实战项目完整的后台管理系统作者小张 | 转载请注明出处