news 2026/7/30 4:03:21

Vue3(篇五)pinia状态管理——跨组件数据共享

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3(篇五)pinia状态管理——跨组件数据共享

目录

  • 前言
  • 一、为什么需要状态管理?
    • 1.1 组件通信的困境
    • 1.2 状态管理的本质
  • 二、Pinia 是什么?
    • 2.1 Pinia vs Vuex
    • 2.2 Pinia 的核心概念
  • 三、安装与配置
    • 3.1 安装
    • 3.2 挂载到应用
  • 四、定义 Store
    • 4.1 基本结构
    • 4.2 组合式 API 风格(推荐)
  • 五、使用 Store
    • 5.1 在组件中使用
    • 5.2 解构 Store
    • 5.3 直接修改 State
    • 5.4 订阅 State 变化
  • 六、实战案例:用户认证
    • 6.1 用户 Store
    • 6.2 登录页面
    • 6.3 布局组件(显示用户信息)
    • 6.4 路由守卫(使用 Store)
  • 七、多个 Store
    • 7.1 学生 Store
    • 7.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 Vuex

Pinia 是 Vue3 官方推荐的状态管理库,相比 Vuex(Vue2 时代的方案):

特性VuexPinia
代码量多(mutations、actions)少(只有 state、actions)
TypeScript支持较弱完美支持
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 安装

npminstallpinia

3.2 挂载到应用

main.ts中:

import{createApp}from'vue'import{createPinia}from'pinia'importAppfrom'./App.vue'importrouterfrom'./router'constapp=createApp(App)app.use(createPinia())app.use(router)app.mount('#app')

四、定义 Store

4.1 基本结构

新建src/stores/user.ts

import{defineStore}from'pinia'// 第一个参数是 store 的唯一标识exportconstuseUserStore=defineStore('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.token=token},clearToken(){this.token=''},asyncfetchUserInfo(){// 调用 API 获取用户信息constdata=awaitrequest.get('/api/user/info')this.name=data.namethis.age=data.agethis.sex=data.sex}}})

类比 Java

  • state类似于类的成员变量
  • getters类似于 getter 方法
  • actions类似于普通方法(可以修改 state)

4.2 组合式 API 风格(推荐)

如果习惯 Composition API,可以用setup语法:

import{defineStore}from'pinia'import{ref,computed}from'vue'exportconstuseUserStore=defineStore('user',()=>{// state(用 ref 包装)constname=ref('')constage=ref(0)consttoken=ref('')// getters(用 computed)constisLogin=computed(()=>!!token.value)// actions(普通函数)functionsetToken(newToken:string){token.value=newToken}functionclearToken(){token.value=''}asyncfunctionfetchUserInfo(){constdata=awaitrequest.get('/api/user/info')name.value=data.name age.value=data.age}// 返回需要暴露的内容return{name,age,token,isLogin,setToken,clearToken,fetchUserInfo}})

两种风格对比

特性Options API 风格Composition API 风格
语法类似 Vuex更灵活,符合 Vue3 风格
复杂度简单适中
推荐场景简单 Store复杂逻辑、需要组合其他 Hook

五、使用 Store

5.1 在组件中使用

<script setup lang="ts"> 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-if="userStore.isLogin">已登录</p> <button @click="userStore.clearToken">退出登录</button> </div> </template>

5.2 解构 Store

直接解构会失去响应式,需要用storeToRefs

<script setup lang="ts"> 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> </template>

5.3 直接修改 State

<script setup lang="ts"> 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 lang="ts"> 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 用户 Store

import{defineStore}from'pinia'import{ref,computed}from'vue'importrequestfrom'@/api/request'exportconstuseUserStore=defineStore('user',()=>{consttoken=ref(localStorage.getItem('token')||'')constname=ref('')constrole=ref('')constisLogin=computed(()=>!!token.value)asyncfunctionlogin(username:string,password:string){constdata=awaitrequest.post('/api/login',{username,password})token.value=data.token name.value=data.name role.value=data.role localStorage.setItem('token',data.token)}asyncfunctionfetchUserInfo(){if(!token.value)returnconstdata=awaitrequest.get('/api/user/info')name.value=data.name role.value=data.role}functionlogout(){token.value=''name.value=''role.value=''localStorage.removeItem('token')}return{token,name,role,isLogin,login,logout,fetchUserInfo}})

6.2 登录页面

<script setup lang="ts"> 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 class="login"> <h2>登录</h2> <el-form> <el-form-item> <el-input v-model="form.username" placeholder="用户名" /> </el-form-item> <el-form-item> <el-input v-model="form.password" type="password" placeholder="密码" /> </el-form-item> <el-button type="primary" @click="handleLogin">登录</el-button> </el-form> </div> </template>

6.3 布局组件(显示用户信息)

<script setup lang="ts"> 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 class="header"> <div class="logo">学生管理系统</div> <div class="user-info"> <span>{{ userStore.name }}</span> <span class="role">{{ userStore.role }}</span> <button @click="handleLogout">退出</button> </div> </header> </template>

6.4 路由守卫(使用 Store)

import{useUserStore}from'@/stores/user'router.beforeEach((to,from,next)=>{constuserStore=useUserStore()if(to.meta.requiresAuth&&!userStore.isLogin){next('/login')}else{next()}})

七、多个 Store

实际项目会有多个 Store,按功能划分:

src/stores/ ├── user.ts # 用户相关 ├── student.ts # 学生相关 └── app.ts # 应用配置(主题、语言等)

7.1 学生 Store

import{defineStore}from'pinia'import{ref}from'vue'importrequestfrom'@/api/request'exportconstuseStudentStore=defineStore('student',()=>{conststudents=ref<Student[]>([])consttotal=ref(0)constloading=ref(false)asyncfunctionfetchStudents(params:QueryParams){loading.value=truetry{constdata=awaitrequest.get('/api/students/page',{params})students.value=data.list total.value=data.total}finally{loading.value=false}}asyncfunctiondeleteStudent(id:number){awaitrequest.delete(`/api/students/${id}`)// 刷新列表awaitfetchStudents({page:1,size:10})}return{students,total,loading,fetchStudents,deleteStudent}})

7.2 在组件中使用多个 Store

<script setup lang="ts"> 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 :data="studentStore.students" v-loading="studentStore.loading"> <!-- ... --> </el-table> </div> </template>

八、Pinia 持久化

默认情况下,Pinia 的数据保存在内存中,刷新页面会丢失。可以通过插件实现持久化。

8.1 安装插件

npminstallpinia-plugin-persistedstate

8.2 配置插件

import{createApp}from'vue'import{createPinia}from'pinia'importpiniaPluginPersistedstatefrom'pinia-plugin-persistedstate'importAppfrom'./App.vue'constpinia=createPinia()pinia.use(piniaPluginPersistedstate)constapp=createApp(App)app.use(pinia)app.mount('#app')

8.3 在 Store 中启用持久化

exportconstuseUserStore=defineStore('user',{state:()=>({token:'',name:''}),// 启用持久化persist:true})

或者自定义持久化配置:

exportconstuseUserStore=defineStore('user',{state:()=>({token:'',name:'',role:''}),persist:{key:'my-user-store',// 存储 keystorage:localStorage,// 存储方式(localStorage 或 sessionStorage)paths:['token','name']// 只持久化部分字段}})

九、Pinia vs 直接使用 localStorage

维度PinialocalStorage
响应式✅ 自动更新❌ 需要手动监听
类型安全✅ TypeScript 支持❌ 纯字符串
调试✅ Vue DevTools 支持❌ 不支持
持久化需要插件✅ 天然支持
适用场景运行时状态长期存储

推荐:运行时状态用 Pinia,需要持久化时配合插件使用。

十、总结

本文系统讲解了 Pinia 状态管理:

概念作用关键点
Store管理共享数据defineStore定义
state数据本身ref包装(Composition API)
getters计算属性computed
actions操作方法普通函数,可修改 state
storeToRefs解构保持响应式只用于 state 和 getters

最佳实践

  1. 每个 Store 独立文件,按功能划分
  2. 复杂 Store 使用 Composition API 风格
  3. 需要持久化时使用pinia-plugin-persistedstate
  4. 解构 Store 时用storeToRefs保持响应式

Vue3 学习路径总结

通过这五篇博客,我们已经系统掌握了 Vue3 的核心知识:

序号博客核心内容
1Vue3入门:响应式数据与数据绑定环境搭建、ref/reactive、数据绑定、计算属性、条件/列表渲染
2与后端交互:axios与请求封装axios 基础、拦截器、环境变量、vue-request
3Element Plus实战:快速构建增删改查表格、分页、表单、校验、子组件通信
4vue-router:单页面应用路由管理路由配置、嵌套路由、路由守卫
5pinia状态管理:跨组件数据共享Store 定义与使用、持久化

下一步学习建议

  • TypeScript 进阶:泛型约束、类型体操、工具类型
  • Vue3 进阶:自定义 Hook、指令、插件
  • 工程化:Vite 配置、打包优化、CI/CD
  • 实战项目:完整的后台管理系统

作者:小张 | 转载请注明出处

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 4:02:55

STM32高级定时器配置三相六路PWM互补输出与死区时间详解

1. 项目概述&#xff1a;为什么三相PWM互补输出是FOC的基石搞电机FOC控制&#xff0c;第一步往往不是写算法&#xff0c;而是把硬件底层的PWM波形给整明白、配利索。很多朋友在STM32上跑FOC&#xff0c;卡住的第一步常常就是CUBEMX里那一堆定时器配置&#xff0c;看着高级定时器…

作者头像 李华
网站建设 2026/7/30 4:02:50

电机控制参数辨识:从模型匹配到精准控制的关键技术

1. 从“调参玄学”到“心中有数”&#xff1a;为什么参数辨识是电机控制的基石干了这么多年电机控制&#xff0c;最怕听到的一句话就是&#xff1a;“这电机怎么调都调不好&#xff0c;参数是不是有问题&#xff1f;” 尤其是在做永磁同步电机&#xff08;PMSM&#xff09;或者…

作者头像 李华
网站建设 2026/7/30 3:59:24

Python图像处理:傅里叶变换与频率域滤波实战指南

1. 项目概述&#xff1a;从像素到频率的视角转换搞图像处理&#xff0c;如果你还停留在“这个像素是啥颜色&#xff0c;那个像素怎么改”的层面&#xff0c;那可能还没摸到真正的门道。我干了这么多年&#xff0c;发现很多朋友在处理图像去噪、边缘增强或者模糊效果时&#xff…

作者头像 李华
网站建设 2026/7/30 3:59:17

2026年智能客服系统选型指南:核心能力与技术解析

1. 2026年客服智能体服务商选型的核心能力解析在客户服务领域&#xff0c;智能客服系统已经从简单的问答机器人发展为能够处理复杂交互的智能体。随着2026年的临近&#xff0c;企业在选择客服智能体服务商时需要关注的核心能力也在不断演进。作为一位在客户服务技术领域深耕多年…

作者头像 李华
网站建设 2026/7/30 3:55:57

C#学习路线图:从零到精通的系统化进阶指南

1. 项目概述&#xff1a;为什么需要一份C#学习路线图&#xff1f; 如果你刚接触C#&#xff0c;或者已经写了几行代码但感觉知识体系像一盘散沙&#xff0c;那你来对地方了。我见过太多开发者&#xff0c;包括当年的我自己&#xff0c;一头扎进C#的世界&#xff0c;从“Hello W…

作者头像 李华
网站建设 2026/7/30 3:53:04

FPGA开发基础:寄存器与组合逻辑的协同设计与实战应用

FPGA作为可编程逻辑器件的核心代表&#xff0c;其内部硬件电路主要由寄存器和组合逻辑两大基本单元构成。理解这两类电路的工作原理和协同机制&#xff0c;是掌握FPGA开发设计的关键基础。无论是简单的逻辑控制还是复杂的数字系统&#xff0c;都离不开寄存器对时序的控制和组合…

作者头像 李华