一文带你了解Vue全局状态管理
1. 背景
- 在前端开发中,状态管理始终是架构设计的核心议题之一。早期应用通常依赖全局变量与直接的 DOM 操作维护状态:在页面规模较小时尚可工作,但随着 SPA 普及与业务复杂度上升,状态逐渐分散在多个组件与服务调用链路中,导致"数据从哪里来、被谁改过、何时改的"难以追踪,跨组件共享与异步副作用也更难治理。为应对这些问题,业界逐步形成了以 单向数据流 、集中式/半集中式存储 、可追踪变更 为目标的状态管理方案,如 MobX、Redux、Vuex、Pinia、Zustand、Recoil 等。
2. Vuex
2.1 Vuex 主要是为了解决什么问题?
-
在 Vue 2 时代,当应用稍微变大时,会立刻遇到几个痛点:
-
跨组件共享状态极其麻烦
-
user / cart / token 等状态在很多页面和组件里都要用。
-
对于相邻的父子组件、对于多层级嵌套的场景下,你要么在根组件维护,然后一层层
props 往下传,结构一变,整条传参链都要改;要么搞一个 event bus,到处$emit /$on。
-
-
状态修改没有规范和约束,调试困难
-
任意组件都可以随便改一个"全局对象"(比如
window.user、this.$root.xxx),出问题的时候,你根本不知道是哪个组件在什么时候把它改坏的 。window.user这类全局对象 不在 Vue 响应式体系内,改了不一定触发视图更新this.$root.xxx如果随处可写,就会出现"改坏了但不知道谁改的"- Vue 2 还有一个额外坑:给响应式对象新增属性 需要
Vue.set / this.$set才能被追踪
-
-
复杂业务的异步逻辑散落在各处
- 请求逻辑、缓存逻辑、权限逻辑都写在组件里;
- 一份业务规则要在多个组件拷贝/同步,难以复用。
-
-
Vuex 的解决思路:
-
统一搞一个 全局 store :所有全局状态放在 state对象 里。
我们经常说要把数据交给Vuex进行管理,其实就是把数据交给Vuex里面的State对象进行保管
-
规定所有同步修改必须通过 mutation ;异步/业务逻辑通过 action。
-
提供 getter 做派生状态(mapGetters)
不要把所有需要用到的值都直接存进 state,而是只存"最小必要的原始状态",
对于那些可以由原始状态计算出来的"衍生信息",统一通过 Vuex 的 getter 封装和计算。
-
配合 DevTools 做时间旅行调试(每次 mutation 都能记录和回放)。
-
2.2 Vuex基本概念
-
概念 :Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
-
Vuex它的背后基本思想借鉴了Flux。Flux 是 Facebook 在构建大型 Web 应用程序时为了解决数据一致性 问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构 (如Redux, MobX, Recoil, Zustand, otai, Vuex, Pinia)

-
Flux 架构主要有四个组成部分:
- store:状态数据的存储管理中心,可以有多个,可以接受 action 做出响应。
- view:视图,根据 store 中的数据渲染生成页面,与 store 之间存在发布订阅关系。
- action:一种描述动作行为的数据对象,通常会包含动作类型 type 和需要传递的参数 payload 等属性。
- dispatcher :调度器,接收 action 并分发至 store。

-
Flux 架构最核心的特点:单向数据流。
-
-
单向数据流
-
状态,驱动应用的数据源;
-
视图 ,以声明方式将状态映射到视图;
-
操作 ,响应在视图 上的用户输入导致的状态变化

-
我对单向数据流的理解是应用里数据的"产生、更新、传播到界面"的路径只有一个固定方向 ,而不是在多个方向来回修改同一份状态。用链路来表示:View(用户交互) → Action(发起意图) → Mutation(落库修改) → State 更新 → View 重新渲染, 你可以把它理解成"数据的高速公路只有一条,别在路上逆行" 。
整个数据流动关系为:
1、view 视图中的交互行为会创建 action,交由 dispatcher 调度器。
2、dispatcher 接收到 action 后会分发至对应的 store。
3、store 接收到 action 后做出响应动作,并触发 change 事件,通知与其关联的 view 重新渲染内容。
-
-
原理图

-
Vuex在沿用 Flux 的思想的基础上针对 Vue 框架单独进行了一些设计上的优化,Vuex主要由四部分组成:
-
State:状态,Vuex 使用单一状态树------Vuex用一个对象就包含了全部的应用层级状态。至此它便作为一个"唯一数据源 "而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段 -
Getter:有时候我们需要从 store 中的 state 中派生出一些状态,此时会使用到Getter。Getter等效于 Vue 组件中的计算属性。它会自动收集依赖,以实现计算属性的缓存。 -
Mutations:更改Vuex 的store 中的状态的唯一方法是提交mutation。作用是:修改、加工、维护数据。在 Vuex 中,mutation 都是同步事务 : -
Actions:动作、行为。使用场景:涉及到调用异步 API 和分发多重 mutation:-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。
jsactions: { checkout ({ commit, state }, products) { // 把当前购物车的物品备份起来 const savedCartItems = [...state.cart.added] // 发出结账请求 // 然后乐观地清空购物车 commit(types.CHECKOUT_REQUEST) // 购物 API 接受一个成功回调和一个失败回调 shop.buyProducts( products, // 成功操作 () => commit(types.CHECKOUT_SUCCESS), // 失败操作 () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) } }
-
-
Module:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块------从上至下进行同样方式的分割
-
-
2.3 Vuex的使用
-
对于大型应用,
Vuex 会希望把Vuex相关代码分割到模块中。下面是项目结构示例:js├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── components │ ├── App.vue │ └── ... └── store ├── index.js # 我们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块 -
基本使用
-
store/index.js:把各个模块、插件和全局配置组装成一个store实例并导出jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions = {} const mutations = {} const state = {} export default new Vuex.Store({ actions, mutations, state }) -
在
main.js中创建vm时传入store配置项js...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store }) -
store/index.js:初始化数据、配置actions、配置mutationsjsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) // 配置actions const actions = { addWait(context,value){ setTimeout(()=>{ context.commit('add',value) },500) } } // 配置mutations const mutations = { add(state,value){ state.sum += value } } // 配置状态 const state = { sum:0 } export default new Vuex.Store({ actions, mutations, state, }) -
components/AppCount.vue- 通过
this.$store.dispatch 调用 action 中的方法 - 通过
this.$store.commit 调用 mutations 中的方法 - 通过
$store.state.sum 读取 state
js<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script> export default { name:'AppCount', data() { return { } }, methods: { addNumber(){ this.$store.commit('add',1) }, addNumberWait(){ this.$store.dispatch('addWait',1) } }, } </script> <style lang="css"> button{ margin-right: 5px; } </style>> - 通过
-
-
Getter:有时候我们需要从 store 中的 state 中派生出一些状态,此时会使用到Getter-
store/index.jsjsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) ... // 新增getter对象 const getters = { bigSum(state){ return state.sum * 10 } } ... const state = { sum:0 } export default new Vuex.Store({ ... getters // 导出getter }) -
components/AppCount.vuejs<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <!-- 读取getter内容 --> <h1>当前大十倍:{{$store.getters.bigSum}}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script> export default { name:'AppCount', data() { return { } }, methods: { addNumber(){ this.$store.commit('add',1) }, addNumberWait(){ this.$store.dispatch('addWait',1) } }, } </script> <style lang="css"> button{ margin-right: 5px; } </style>>
-
-
模块化
-
Module:当项目变大,单一 store 会臃肿。Vuex 提供 modules 来拆分。 -
基本使用
-
代码目录
js. ├── App.vue ├── components │ └── AppCount.vue ├── main.js └── store ├── index.js └── modules ├── code.js └── count.js -
在初始化实例的时候,
Store允许我们传入modules字段jsexport interface StoreOptions<S> { state?: S | (() => S); getters?: GetterTree<S, S>; actions?: ActionTree<S, S>; mutations?: MutationTree<S>; modules?: ModuleTree<S>; // 传入modules ✅ plugins?: Plugin<S>[]; strict?: boolean; devtools?: boolean; } -
store/index.js:在modules,我们可能放多个对象jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) import countModules from './modules/count' import codeModules from './modules/code' export default new Vuex.Store({ modules: { // 配置modules字段 ✅ countModules, codeModules } }) -
store/modules/count.jsjsexport default { actions: { addWait(context, value) { setTimeout(() => { context.commit("add", value); }, 500); }, }, mutations: { add(state, value) { state.sum += value; }, }, getters: { bigSum(state) { return state.sum * 10; }, }, state: { sum: 0, }, };
-
-
支持模块相互嵌套
-
src/store/modules/code.jsjs// hlg:6; import codexModules from './codex' export default { namespaced: true, modules: { codexModules, // ✅ 新增子模块 }, ... }; -
在可以直接通过
store读到嵌套模块的数据js// hlg:3; hlg:6; // 1. 在模板里直接读 <h1>codexModules的代码行数为:{{ $store.state.codeModules.codexModules.codexNum }} </h1> // 2. 在 Composition API 里用 computed const store = useStore() const codexNum = computed(() => store.state.codeModules.codexModules.codexNum) // 其他方式同理
-
-
动态注册模块
-
可以使用
module.registerModule动态注册模块。实现按需引入 -
代码实现
-
src/store/index.js:你需要改到Vuex Store 的入口文件,并且对外暴露动态注册、动态注销的方法js// hlg:5; hlg:10; hlg:19 const store = createStore({ modules: { // ✅ 静态注册你"始终需要"的模块 countModules } }) // ✅ 动态注册入口:可在 main.js 或某个页面/组件进入时调用 export function registerCodeModules() { // Vuex4 支持 hasModule,用它避免重复注册 if (!store.hasModule('codeModules')) { store.registerModule('codeModules', codeModules) } } // ✅ 动态注销该模块 export function unregisterCodeModules() { if (store.hasModule('codeModules')) { store.unregisterModule('codeModules') } } // ✅ 默认导出 store 实例 export default store -
使用场景:
-
你可以在应用启动时就注册按需注册某个模块。 既然你选择"启动即加载",通常意味着这个模块会贯穿整个应用生命周期。因此一般来说不需要卸载
js// src/main.js import store, { registerCodeModules } from './store' // ✅ 在应用启动时动态注册 registerCodeModules() const app = createApp(App) app.use(store) app.mount('#app') -
当然,你也可以在进入组件时才动态注册
-
在路由守卫
beforeEnter时动态注册jsimport AppCount from '@/components/AppCount.vue' import { registerCodeModules } from '@/store' const routes = [ { path: '/count', component: AppCount, beforeEnter: () => { registerCodeModules() return true }, }, ] -
离开组件路由时
beforeRouteLeave卸载jsimport { unregisterCodeModules } from '@/store' export default { beforeRouteLeave(to, from, next) { unregisterCodeModules() next() }, }
-
-
-
-
-
-
命名空间
-
命名空间:在 Vuex 中开启
namespaced: true 的作用是:把该模块的 state/getters/mutations/actions "隔离到自己的命名空间"里,从而避免与其他模块发生命名冲突 -
如果你不开启命名冲突,你根本不知道你调用的是啥。
$store.getters.bigSum: 会执行模块注册顺序第一个实现getter方法的store里的getter返回的数据this.$store.commit('add',1)、this.$store.dispatch('addWait',1): 会按照模块注册顺序,执行顺序actions、mutations方法
-
开启命名空间之后,你就可以分模块去调用对应的的
getters、commit、dispatchjs// hlg:6; hlg:23; hlg:26; <template> <div> <h1>当前求和为:{{$store.state.countModules.sum}}</h1> <!-- 读取getter内容 --> <h1>当前大十倍:{{$store.getters["countModules/bigSum"]}}</h1> <h1>当前代码行数为:{{$store.state.codeModules.codeNum}}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script> export default { name:'AppCount', data() { return { } }, methods: { addNumber(){ this.$store.commit('countModules/add',1) }, addNumberWait(){ this.$store.dispatch('countModules/addWait',1) } }, } </script> <style lang="css"> button{ margin-right: 5px; } </style>>
-
-
map方法
-
由于每次
this.$store.xxx非常麻烦,产生大量重复无用代码,因此Vuex提供了4个辅助函数mapState /mapGetters /mapActions /mapMutations -
mapState:把store.state 映射成组件的 computed(响应式读取)。 -
mapGetters:把store.getters 映射成组件的 computed。 -
mapActions:把store.dispatch() 映射成组件的 methods。 -
mapMutations:把store.commit() 映射成组件的 methods。 -
代码示例
js// hlg:4; hlg:6; hlg:23; hlg:24; hlg:27; hlg:28; hlg:30; hlg:33; <template> <div> <h1>当前求和为:{{sum}}</h1> <!-- 读取getter内容 --> <h1>当前大十倍:{{bigSum}}</h1> <h1>当前代码行数为:{{$store.state.codeModules.codeNum}}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script> import { mapState, mapGetters, mapMutations, mapActions } from 'vuex' export default { name:'AppCount', data() { return { } }, computed: { ...mapState('countModules', ['sum']), ...mapGetters('countModules', ['bigSum']), }, methods: { ...mapMutations('countModules', ['add']), ...mapActions('countModules', ['addWait']), addNumber(){ this.add(1); }, addNumberWait(){ this.addWait(1) } }, } </script> <style lang="css"> button{ margin-right: 5px; } </style>> -
因为这些
mapXxx 辅助函数是为 Options API 设计的,内部依赖组件实例的this.$store;而在Vue3 Composition APIsetup() 里没有this。一次Vue3 Composition API 不能使用mapXxx这些辅助函数
-
-
Vue3
Composition API 中使用store-
在Vue2中,安装的一般是Vuex 3.xxx的版本。Vuex 3.xxx的版本主要是用于 Vue 2 生态
-
而如果项目是Vue3,需要安装Vuex 4,Vuex 4重点支持 Vue 3,并尽量保持与 Vuex 3 相同 API 以便复用代码
-
基本使用
-
main.js:通过app.use(store) 把store 这个插件挂到当前应用实例app上jsimport { createApp } from 'vue' import App from './App.vue' import store from './store' const app = createApp(App) app.use(store) app.mount('#app') -
store里的所有代码不需要改动
-
src/components/AppCount.vue:Vuex 4 引入了一个新的 API 用于在组合式 API 中与 store 进行交互。可以在组件的 setup 钩子函数中使用 useStore 组合式函数来检索 store。通过const store = useStore()获取store实例js// hlg:21; hlg:24; hlg:27; hlg:30; hlg:8; <template> <div> <h1>当前求和为:{{ sum }}</h1> <!-- 读取getter内容 --> <h1>当前大十倍:{{ bigSum }}</h1> <h1>当前代码行数为:{{ $store.state.codeModules.codeNum }}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script setup> import { computed } from 'vue' import { useStore } from 'vuex' const store = useStore() // 等价于 Vue2 的:...mapState('countModules', ['sum']) const sum = computed(() => store.state.countModules.sum) // 等价于 Vue2 的:...mapGetters('countModules', ['bigSum']) const bigSum = computed(() => store.getters['countModules/bigSum']) // 等价于 Vue2 的:...mapMutations('countModules', ['add']) const add = (value) => store.commit('countModules/add', value) // 等价于 Vue2 的:...mapActions('countModules', ['addWait']) const addWait = (value) => store.dispatch('countModules/addWait', value) function addNumber() { add(1) } function addNumberWait() { addWait(1) } </script> <style lang="css"> button { margin-right: 5px; } </style>
-
-
注意:直接在JS代码里执行
const sum1 = store.state.countModules.sum; 或者解构const { sum } = store.state.countModules 获取到的是数值基本类型(getter也是同理),如果需要将获取到的值直接绑定到模板是不具备响应式的 ,如果你解出来的数据需要响应式可以直接通过toRefs先将store的数据变成响应式(或者使用toRef、computed、watch)js// 直接解构,如果直接赋值是不具备相应是的 const sum1 = store.state.countModules.sum; const { sum } = store.state.countModules // 方法1 const { sum } = toRefs(store.state.countModules); // 方法2 const sum = toRef(store.state.countModules, 'sum') // 方法3:computed ...略 // 方法4:watch ...略
-
3. Pinia
3.1 背景

Vue 的官方状态管理库已经改为 Pinia。Pinia 的 API 与 Vuex 5(在 Vuex 5 RFC 中描述)几乎完全相同,甚至有所增强。你可以把 Pinia 简单理解为"改了名字的 Vuex 5"。Pinia 也同样支持 Vue 2.x。
- 从官方的话可以知道Pinia 吸收了 Vuex 5 RFC 的设计方向,并最终成为 Vue 官方推荐的状态管理方案。 目前Pinia GitHub仓库已经纳入vuejs组织下

- 官方文档:pinia.vuejs.org/
- 为什么取名 Pinia? Pinia是西班牙语中Piña(菠萝)

- Pinia是什么? Pinia The intuitive store for Vue.js
- Pinia设计理念 : Type Safe, Extensible, and Modular by design. Forget you are even using a store.

3.2 Pinia组成
-
Pina主要有三个组成部分:
- state:store 的核心,与 Vue 中的 data 一致,可以直接对其中的数据进行读写。
- getters:与 Vue 中的计算属性相同,支持缓存。
- actions:操作不受限制,可以创建异步任务,可以直接被调用,不再需要 commit、dispatch 等方法。可在action中直接改 state
下面我们来看看Pinia主要解决了什么问题?
3.3 Pinia 主要是为了解决什么问题?
-
Pinia 主要解决的问题如下:
- 去掉
mutations的外部接口,减少冗余分层 - 更贴合 Composition API :
useXxxStore()+storeToRefs(),语法更简洁 - 减少"魔法字符串路径",提升重构可靠性
- 扁平化多 store 结构,降低模块嵌套与动态注册心智负担
- 降低 TypeScript 使用成本,更多依赖类型推导
- 去掉
-
改进1: 去掉
mutations,逻辑更聚合,mutations被认为非常冗长,因此在Pinia中不再存在mutationsPinia 并非完全抛弃了
mutation,而是将对state 中单个数据进行修改的操作封装为一个mutation,但不对外开放接口。可以在devtools 中观察到mutation。-
Vuex 4:必须拆 mutation + action:
jsmutations: { add(state, value) { state.sum += value } }, actions: { addWait({ commit }, value) { setTimeout(() => commit('add', value), 500) } } -
Pinia:直接在 action 里改 state:
jsactions: { add(value) { this.sum += value }, addWait(value) { setTimeout(() => this.add(value), 500) } }
-
-
改进2: 语法更简洁,提供 Composition-API 风格 API
-
Vuex 4
-
需要写大量重复代码
store.state.xxx、store.getters.xxx、store.commit('xxx', value)、store.dispatch('xxx', xxx) -
Composition API:useStore + computed + commit/dispatch
-
Composition API:useStore + ToRefs
js<script setup> import { computed } from 'vue' import { useStore } from 'vuex' const store = useStore() const sum1 = computed(() => store.state.countModules.sum) const { sum } = store.state.countModules const bigSum = computed(() => store.getters['countModules/bigSum']) const add = (value) => store.commit('countModules/add', value)
-
-
Pinia
-
useCounterStore + storeToRefs + 直接调用 action
js<script setup> import { computed } from 'vue' import { storeToRefs } from 'pinia' import { useCountModulesStore } from '@/stores/countModules' const store = useCountModulesStore() // ✅ 等价:computed(() => store.state.countModules.sum) const sum1 = computed(() => store.sum) // ✅ 等价:const { sum } = store.state.countModules 但保持响应式 const { sum, bigSum } = storeToRefs(store) // ✅ 等价:commit('countModules/add', value) const add = (value) => store.add(value) </script>
-
-
-
改进3: 解决"magic strings(字符串路径)多、重构脆弱":直接 import store + 自动补全
-
Pinia 官方明确写了:No more magic strings (不再靠字符串注入/路径),改为"导入函数、调用、享受自动补全"。

-
而 Vuex 4.xx 的模块/命名空间场景下,经常需要
'a/b/c/someGetter'这种路径字符串,官方模块文档也提到会变得冗长。 -
Vue4
jsconst bigSum = computed(() => store.getters['countModules/bigSum']) store.dispatch('countModules/addWait', 1) store.commit('countModules/add', 1) -
Pinia
jsconst { sum, bigSum } = storeToRefs(countStore) countStore.addWait(1) countStore.add(1)
-
-
改进4: 解决"嵌套 modules + namespaced 心智负担":扁平多 store 结构,天然隔离。在Pinia中不需要动态添加 Store,它们默认是动态的。
-
不再需要嵌套模块结构 ,默认扁平;不再需要 namespaced modules,因为 store 的命名空间天然由 store id 决定。
-
Vuex 的模块体系支持嵌套与命名空间,并提供 rootState/rootGetters 等机制。
-
Vuex4必须集中在一个 createStore 里注册 modules,或者自己写动态注册的逻辑
js// 保留注册方法 const store = createStore({ modules: { countModules } }) export function registerCodeModules() { // Vuex4 支持 hasModule,用它避免重复注册 if (!store.hasModule('codeModules')) { store.registerModule('codeModules', codeModules) } } // 业务使用时注册Modules import store from '@/store' import codeModules from '@/store/modules/code' store.registerCodeModules('codeModules', codeModules) -
Pinia不需要集中注册:
-
stores/countModules.js就是一个 store -
stores/codeModules.js就是一个 store -
用到哪里就
useXXXStore(),天然按需引入jsimport { useCodeModulesStore } from '@/stores/codeModules' const codeStore = useCodeModulesStore() // 用到即生效
-
-
-
改进5: 解决"TypeScript 支持成本高":尽量靠类型推导而不是自定义复杂封装,不需要为 TS 创建复杂封装;
3.4 Pinia的使用
-
src/main.js: 创建一个 pinia 实例,把 pinia 注入到整个应用createPinia()- Pinia API:创建一个 pinia 实例(全局 store 容器)。
- 这个实例会被 app.use(pinia) 安装到 Vue 应用中。
jsimport { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) // 创建一个 pinia 实例 const pinia = createPinia() app.use(pinia) app.mount('#app') -
src/stores/counter.js:通过defineStore定义Store,传入id作为store 的唯一标识。在组件中调用useXXXStore() 获取store实例(响应式)。-
Composition API 风格
可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
jsimport { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCounterStore = defineStore('counter', () => { // state const sum = ref(0) // getters const bigSum = computed(() => sum.value * 10) // actions function add(value) { sum.value += value } function addWait(value) { setTimeout(() => { add(value) }, 500) } return { sum, bigSum, add, addWait } }) -
Options API 风格
也可以传入一个带有 state、actions 与 getters 属性的 Option 对象
jsimport { defineStore } from 'pinia' // 定义Store export const useCounterStore = defineStore('counter', { // state state: () => ({ sum: 0 }), // getters getters: { bigSum: (state) => state.sum * 10 }, // actions actions: { add(value) { this.sum += value }, addWait(value) { // 保持你原项目的行为:延迟 500ms 再 +value setTimeout(() => { this.add(value) }, 500) } } })

3. 在 Pinia 的 setup store(Composition API 风格)内部,直接使用 Vue 的 watch / watchEffect 去监听 store 的状态(ref/computed),一旦状态变化就自动执行副作用逻辑 (例如:持久化、请求接口、同步路由、打点、节流防抖等)。这类 watcher 不需要写在组件里 ,因此只要 store 被创建一次,监听逻辑就会持续生效(对所有使用该 store 的组件都统一生效)。js// stores/counter.js import { defineStore } from 'pinia' import { ref, computed, watch } from 'vue' export const useCounterStore = defineStore('counter', () => { // state const sum = ref(0) // 初始化:从 localStorage 读回(注意 SSR 需要做 window 判断) if (typeof window !== 'undefined') { const cached = window.localStorage.getItem('sum') if (cached != null) sum.value = Number(cached) || 0 } // getter const bigSum = computed(() => sum.value * 10) // actions function add(value) { sum.value += value } function addWait(value) { setTimeout(() => add(value), 500) } // watcher:监听 sum 变化,自动持久化 watch( sum, (newVal, oldVal) => { // 这里就是"store 内创建侦听器"的含义:状态变了,副作用自动执行 if (typeof window !== 'undefined') { window.localStorage.setItem('sum', String(newVal)) } // 你也可以在这里做打点、日志、联动请求等 // console.log(`[counter] sum: ${oldVal} -> ${newVal}`) }, { flush: 'post' } // 可选:让回调在组件更新后触发(非必须) ) return { sum, bigSum, add, addWait } }) // App.vue <script setup> import { useCounterStore } from './stores/counter' const counter = useCounterStore() </script> <template> <div> <p>sum: {{ counter.sum }}</p> <p>bigSum: {{ counter.bigSum }}</p> <button @click="counter.add(1)">+1</button> <button @click="counter.addWait(5)">500ms +5</button> </div> </template>
-
-
src/components/AppCount.vue:使用useXxxStore()js<template> <div> <h1>当前求和为:{{ sum1 }}</h1> <!-- 读取 getter 内容(Pinia getter 可直接当作响应式值使用) --> <h1>当前大十倍:{{ bigSum }}</h1> <h1>codeModules的代码行数为:{{ codeNum }}</h1> <h1>codexModules的代码行数为:{{ codexNum }}</h1> <button @click="addNumber">+1</button> <button @click="addNumberWait">等1秒加1</button> </div> </template> <script setup> import { storeToRefs } from 'pinia' import { useCounterStore } from '../stores/counter' import { useCodeStore } from '../stores/code' import { useCodexStore } from '../stores/codex' /** * useXxxStore() * - Pinia API:获取/创建 store 实例(按需创建)。 * - store 实例本身是响应式对象。 */ const counterStore = useCounterStore() const codeStore = useCodeStore() const codexStore = useCodexStore() /** * storeToRefs(store) * - Pinia API:把 store 上的 state/getters 转为 refs,便于解构且不丢响应式。 * - 推荐用法:const { a, b } = storeToRefs(store) */ const { sum, bigSum } = storeToRefs(counterStore) const { codeNum } = storeToRefs(codeStore) const { codexNum } = storeToRefs(codexStore) // 保持你原模板变量名 sum1(原来是 computed),这里直接复用 sum 这个 ref 即可 const sum1 = sum function addNumber() { // Pinia:直接调用 action(不再 commit) counterStore.add(1) } function addNumberWait() { // Pinia:直接调用 action(不再 dispatch) counterStore.addWait(1) } </script> <style lang="css"> button { margin-right: 5px; } </style>
4. 参考文档
facebookarchive.github.io/flux/?utm_s...
blog.isquaredsoftware.com/presentatio...