Vue状态管理:从Vuex平滑过渡到Pinia

0 阅读9分钟

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提交mutationsmutations同步修改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 项目开发中请求、路由、状态三大核心工程化能力。