Vue三板斧终篇:从Vuex完整吃透,进阶零成本拿下Pinia
Vue开发,组件通信是日常开发的重中之重。
父子传值、爷孙传值、兄弟传值,小型项目随便写都不乱。但一旦项目体量上来,多个页面、跨层级组件共用数据时,组件传值就会变得又乱又繁琐。这时候,全局状态管理就该登场了!
Vue2 时代, Vuex 是官方钦定的唯一状态管理方案,几乎是所有老项目的标配。但用过的都懂:规矩多、流程绕、代码冗余,写起来属实折磨人。
于是 Vue3 时代,官方直接推出Pinia,全方位平替 Vuex。语法极简、逻辑清晰、适配 setup 语法糖,如今已是Vue3项目的绝对主流。
今天咱们溯源学习、新旧对比、实战落地!先彻底吃透Vuex的底层逻辑和数据流,再顺势解锁Pinia的所有优化点,一次性吃透全局状态管理!
一、从零吃透老牌王者:Vuex
在学新东西之前,先把老版本彻底摸透,后续学Pinia完全零成本。
1. Vuex 核心作用
简单来说:Vuex 就是Vue项目的公共数据仓库。 (组件自身的 data/ref 是「私有数据」,只供自己使用, Vuex 存储的是「全局公共数据」,所有组件可读可操作,统一管理、全局响应式。)
这里给插一个通俗比喻,帮大家秒懂整体数据流逻辑: 我们可以把 Vuex 整体比作一家餐厅的后厨仓库体系
- state :仓库原材料(原始数据,原汁原味不加工)
- getters :后厨厨师(原材料二次加工,出成品,不改动原食材)
- mutations :仓库管理员(唯一有权修改库存的人)
- actions :采购调度员(负责外出采购、对接异步任务)
- 页面组件:前厅服务员(只负责取食材、上菜,无权改库存)
下面重点用这个比喻理解数据流
2. Vuex 五大核心模块
Vuex 的所有功能,全部依托五个核心模块实现,分工明确、各司其职:
① state:全局原始数据源
整个仓库的数据地基,存放所有全局共享的原始数据,比如 token 、用户信息、全局配置、列表数据等。
- 特性:全局响应式 - 规则:所有组件可读,禁止直接修改
就像仓库原材料,所有人都能看、能取用,但不能私自篡改库存
② getters:全局计算属性
相当于全局版的 computed ,专门用来加工state原始数据。 不会修改state里的原始值,只是对数据做过滤、计算、格式化、拼接,自带缓存,重复调用不会重复计算。
(业务场景:手机号脱敏、计算购物车总价、筛选有效列表、判断登录状态等。)
③ mutations:唯一同步修改入口
Vuex 硬性规则:只有 mutations 能修改 state 数据。
- 只支持同步代码
- 所有数据变更统一走这里,可被开发者工具精准追踪,方便排错
④ actions:异步逻辑处理器
专门处理异步任务:接口请求、定时器、延时操作等。 自身不能直接修改state,只能处理完异步逻辑后,调用 commit 触发 mutations,间接修改数据。
⑤ modules:模块化拆分
项目数据过多时,单一state会臃肿杂乱。 modules可以把仓库拆分成多个独立小仓库(用户模块、购物车模块、配置模块),代码分层清晰,互不冲突。
3. Vuex 核心数据流
结合上面的后厨比喻,大家一眼就能看懂完整调度逻辑:
服务员(组件)需要更新菜品 → 找调度员(actions)采购 → 调度员对接完毕 → 通知管理员(mutations)改库存 → 库存(state)更新 → 厨师(getters)加工成品 → 前台页面更新
翻译成代码工作流:
组件dispatch触发actions → 异步逻辑处理完毕 → commit提交mutations → mutations同步修改state → 页面响应式更新
这就是Vuex最核心的闭环数据流。
4. Vuex 完整实战代码
1. 仓库配置
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 原始公共数据
state: {
count: 0,
token: '',
userInfo: null
},
// 数据二次加工
getters: {
doubleCount(state) {
return state.count * 2
},
isLogin(state) {
return !!state.token
}
},
// 同步修改数据
mutations: {
SET_COUNT(state, val) {
state.count = val
},
SET_TOKEN(state, token) {
state.token = token
}
},
// 异步业务逻辑
actions: {
async asyncUpdateCount({ commit }) {
// 模拟后端异步请求
const res = await new Promise(resolve => setTimeout(() => resolve(88), 500))
commit('SET_COUNT', res)
}
},
// 模块化拆分(大型项目使用)
modules: {}
})
2. 挂载仓库(main.js)
Vuex配置和挂载是分离的仓库的创建、配置、安装插件,统一写在 store/index.js
main.js 只负责引入、全局挂载,职责拆分更规范。
import Vue from 'vue'
import App from './App.vue'
// 引入全局仓库
import store from './store'
new Vue({
store, // 全局挂载,所有组件可直接使用
render: h => h(App)
}).$mount('#app')
3. 组件使用规范
真实项目中,不会直接在模板/标签里写原生store语法,统一封装自定义事件函数,内部整合状态调用、逻辑判断、路由跳转,是标准写法。
<div<p>原始数值:{{ $store }}</p>
<p>加工后数值:{{ $store.getters.doubleCount }}<button @click="handleSetCount"></button><button @click="handleAsyncCount">异步</button>
</div>
<script>
export default {
methods: {
// 封装统一处理函数:整合状态调用、业务逻辑
handleSetCount() {
// 调用Vuex同步方法
this.$store.commit('SET_COUNT', 20)
},
async handleAsyncCount() {
// 调用Vuex异步方法
await this.$store.dispatch('asyncUpdateCount')
// 可后续追加:逻辑判断、路由跳转、提示弹窗等
}
}</script>
二、深度吐槽:Vuex 为什么被淘汰?
吃透原理后,我们就能精准发现Vuex的致命硬伤,这也是Pinia诞生的核心原因:
1. 流程冗余,写代码像走流程
简单改个数据,必须走 dispatch → actions → commit → mutations 四层流程,纯纯多余工作量,代码重复、冗余严重。
2. 强行拆分同步/异步,毫无意义
Vuex强制:同步写mutations、异步写actions。 实际开发中,这种拆分没有任何业务价值,只会增加学习成本和代码量。
3. 模块化配置繁琐
拆分modules后,必须手动开启 namespaced 命名空间,否则方法、变量会全局冲突,配置繁琐、记忆成本极高。
4. 不适配Vue3语法
Vuex是Vue2产物,依赖 this.$store 调用,和Vue3 script setup 组合式语法格格不入,写法别扭。
5. TS 支持拉胯
类型推断弱、提示差,大型项目无法严格做类型约束,容易产生隐性bug。
总结一句话:Vuex规矩太多、效率太低,跟不上Vue3的开发节奏。
三、全新升级!Pinia 到底优化了什么?
Pinia 不是全新技术,是Vuex的极简进化版! 核心逻辑、核心作用和Vuex完全一致,只是删掉了所有繁琐规则,保留精华、优化体验。
对应刚才的后厨比喻,Pinia的升级超级好理解: 直接把 调度员(actions) 和 管理员(mutations) 合并成一个岗位! 不用中间传话、不用层层报备,调度员处理完任务,直接改库存,效率直接拉满!
Pinia 核心优化点(对标Vuex痛点)
1. 彻底删除 mutations:actions 同时支持同步、异步,可直接修改state
2. 删除多余流程:无需 commit/dispatch ,代码极简
3. 天然模块化:一个 defineStore 就是一个独立仓库,无需配置命名空间
4. 完美适配Vue3 setup语法:写法清爽、贴合组合式API
5. TS 原生友好:类型推断完善,大型项目更稳定
6. 体积更小、API更简洁,学习成本极低
四、上手:Pinia 实战
1. 安装 & 全局挂载
npm install pinia
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia()) // 全局一次性挂载
app.mount('#app')
2. 定义 Pinia 仓库
Pinia 摒弃了繁杂结构,只保留state、getters、actions三大核心,逻辑干净通透。
// src/stores/count.js
import { defineStore } from 'pinia'
// 唯一仓库ID,全局不重复
export const useCountStore = defineStore('count', {
// 原始数据:必须函数返回(避免全局数据污染)
state: () => ({
count: 0,
token: '',
userInfo: null
}),
// 数据加工:和Vuex getters完全一致
getters: {
doubleCount(state) {
return state.count * 2
},
isLogin(state) {
return !!state.token
}
},
// 同步+异步 统一入口
actions: {
// 同步修改
setCount(num) {
// 直接修改state!无需任何commit
this.count = num
},
// 异步修改
async asyncSetCount() {
const res = await new Promise(resolve => setTimeout(() => resolve(99), 500))
this.count = res
}
}
})
3. Pinia 全新数据流
对比Vuex,流程直接腰斩简化: 组件调用actions → 直接修改state → 页面更新
没有多余流程,简洁高效!
4. Pinia 组件标准使用(规范)
在实际项目里,我们一般不会把状态调用直接写在模板里,而是封装成一个业务函数,里面统一处理:修改状态、逻辑判断、路由跳转等操作,这也是最规范、最易维护的写法。
<script setup>
import { useCountStore } from '@/stores/count'
// 拿到 store 实例
const countStore = useCountStore()
// 封装同步修改逻辑
const handleSetCount = () => {
countStore.setCount(30)
}
// 封装异步修改逻辑
const handleAsyncCount = async () => {
await countStore.asyncSetCount()
}
</script>
<template>
<div>
<p>原始数值:{{ countStore.count }}</p >
<p>加工后数值:{{ countStore.doubleCount }}</p >
<button @click="handleSetCount">同步修改数据</button>
<button @click="handleAsyncCount">异步请求数据</button>
</div>
</template>
可以看到,Pinia 写法非常直观: 导入 → 拿到实例 → 调用方法 → 完事 没有多余的 commit / dispatch ,代码干净利落,写起来心情都顺畅很多。
五、Pinia 高频坑
这几个是开发中最容易踩的坑,用最直白的方式讲清楚:
1. 直接解构会丢失响应式
很多人为了省事直接解构,结果发现数据变了,页面不更新:
// ❌ 错误:解构后变成普通变量,失去响应式
const { count } = countStore
正确写法: 用 storeToRefs 把响应式保留下来:
// ✅ 正确:保持响应式解构
import { storeToRefs } from 'pinia'
const { count, token } = storeToRefs(countStore)
2. state 必须写成函数
Vuex 的 state 可以是对象,但 Pinia 必须是函数返回对象:
// 原始数据:必须是函数返回对象(Vue3/ Pinia 规范)
// 原因:确保每个组件实例独立使用这份数据,防止全局污染
state: () => ({
count: 0,
token: '',
userInfo: null
}),
原因很简单: 避免多个组件共用同一份数据,造成全局污染、互相干扰。 和 Vue2 中 data 必须是函数是同一个道理。
3. 批量修改状态用 $patch
如果你一次要改好几个数据,不建议一个个赋值:
// 能跑,但不优雅、性能一般
countStore.count = 66
countStore.token = 'my-token'
推荐用 $patch 批量更新:
// ✅ 推荐:一次修改、只触发一次更新
countStore.$patch({
count: 66,
token: 'my-token'
})
Pinia 还自带一个重置方法,退出登录场景特别好用:
// 重置为 state 初始值
countStore.$reset()
4.场景实战:退出登录时的批量重置
当用户点击“退出”按钮时,我们需要清空所有用户状态并跳回登录页。
// user.js store
import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'
export const useUserStore = defineStore('user', {
state: () => ({ token: '', userInfo: null }),
actions: {
logout() {
// 1. 重置状态
this.$reset()
// 2. 清除本地存储
localStorage.removeItem('token')
// 3. 获取路由实例并跳转
const router = useRouter()
router.push('/login')
}
}
})
六、梳理总结
1. Vuex:规矩多、流程长、冗余重,适合老项目维护。
2. Pinia:简洁、直接、高效,是 Vue3 官方标配。
3. 核心区别:Pinia 删掉了 mutations , actions 直接改 state ,数据流极度清爽。
4. 项目选型:新项目无脑 Pinia,体验提升不止一点。
从繁琐的 Vuex 到简洁的 Pinia,本质是 Vue 生态越来越轻量化、现代化的体现。 看完这一篇,全局状态管理对你来说更彻底通透。
从前两篇的「网络请求全演进」「路由守卫权限管控」,再到本篇 Vuex → Pinia 全局状态管理,我们完整打通了 Vue 项目开发中请求、路由、状态三大核心工程化能力。