状态管理并不只属于大型应用。每一个 Vue 组件都在管理自己的状态:数据决定界面,用户操作再改变数据。真正需要额外设计的时刻,是多个组件开始依赖并修改同一份数据。
本文核对并整理自 Vue 官方中文文档《状态管理》全文,作者归属 Vue.js 文档团队与中文文档贡献者。保留组合式、选项式两类示例与共享状态的技术细节,并补充 SSR 风险及 Pinia 当前版本说明。核对日期:2026 年 10 月 5 日;文中示例只做静态审核,未运行。

一个组件内就有状态、视图和动作
先看一个最小的计数器。组合式 API 用 ref 保存计数,并用函数修改它:
<script setup>
import { ref } from 'vue'
// 状态
const count = ref(0)
// 动作
function increment() {
count.value++
}
</script>
<!-- 视图 -->
<template>{{ count }}</template>
选项式 API 的同一结构如下:
<script>
export default {
// 状态
data() {
return {
count: 0
}
},
// 动作
methods: {
increment() {
this.count++
}
}
}
</script>
<!-- 视图 -->
<template>{{ count }}</template>
两种写法都包含三个部分:状态是数据源;视图以声明式方式映射状态;动作规定状态怎样随交互发生变化。这里的代码只展示这三个部分,还没有把 increment 绑定到按钮。单向数据流可以理解为“状态生成视图,视图中的交互触发动作,动作再更新状态”。
当组件各自独立时,这种结构很清晰。需要共享数据后,则会出现两类问题:多个视图依赖同一份状态,以及不同视图中的交互都需要修改它。
对于前者,可以把状态提升到共同的祖先组件,再通过 props 逐层传下去。这个办法在浅层组件树中很好用,但层级加深后会产生 Prop 逐级透传,中间组件只为了传递数据而增加许多代码。
对于后者,直接用模板引用访问父子组件实例,或靠事件同步多个状态副本,容易让修改路径变得分散。组件之间开始互相了解内部细节,副本也可能失去同步,维护成本随之上升。
一种更直接的办法,是把真正共享的状态抽离出来,在客户端应用中用一个共享实例管理。这样,组件树可以被看作这个状态的多个视图:需要数据的组件读取它,需要改变数据的组件触发动作。
用 reactive 创建一个小型 store
选项式 API 的 data() 所返回的对象,内部会经过 reactive() 转为响应式对象。这个公开 API 也可以直接用于组件之外。创建 store.js:
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
在组合式 API 中,两个组件都导入同一个对象。ComponentA.vue:
<script setup>
import { store } from './store.js'
</script>
<template>From A: {{ store.count }}</template>
ComponentB.vue:
<script setup>
import { store } from './store.js'
</script>
<template>From B: {{ store.count }}</template>
如果使用选项式 API,可以通过 data() 把同一个 store 暴露给模板。ComponentA.vue:
<script>
import { store } from './store.js'
export default {
data() {
return {
store
}
}
}
</script>
<template>From A: {{ store.count }}</template>
ComponentB.vue:
<script>
import { store } from './store.js'
export default {
data() {
return {
store
}
}
}
</script>
<template>From B: {{ store.count }}</template>
这里的两个 data() 并没有复制出两份计数器;它们引用的是同一个响应式 store。当 store.count 改变,两个组件的视图都会更新,共享数据就有了单一来源。
把修改逻辑集中到表达意图的方法中
共享对象带来便利,也意味着任何导入它的组件都能直接修改状态。例如,下面的模板可以工作:
<template>
<button @click="store.count++">
From B: {{ store.count }}
</button>
</template>
简单应用里,这并不难理解。但随着状态增加,如果每个组件都写自己的修改规则,后续就难以找到“计数为什么变化”。因此,原文建议把修改逻辑也集中到 store 中,并让方法名称表达动作的意图:
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
组件改为调用动作:
<template>
<button @click="store.increment()">
From B: {{ store.count }}
</button>
</template>
注意这里是 store.increment(),带有括号,并通过 store 调用。这个函数并不是组件自身的方法,而且实现依赖 this.count;调用时需要保留正确的 this 上下文。不要在没有绑定上下文的情况下,把它拆成一个独立函数直接调用。
审核补充:集中动作首先是一种代码组织约定,并不会自动禁止其他组件继续执行 store.count++。它也不是安全权限边界。客户端状态只能决定界面交互,服务器仍须独立验证实际操作的身份与权限。
全局状态与局部状态由创建位置决定
store 不必是单个 reactive 对象。ref()、computed() 等响应式 API 也可以在组件外使用;组合式函数则能同时返回共享与局部状态:
import { ref } from 'vue'
// 全局状态:创建在模块作用域中
const globalCount = ref(1)
export function useCount() {
// 局部状态:每次调用都会创建
const localCount = ref(1)
return {
globalCount,
localCount
}
}
多个组件调用 useCount() 时,会取得同一个 globalCount,因为它在模块加载时就创建了。localCount 则每次调用都会新建;通常每个组件在 setup 中调用一次,因此各自得到一份局部值。同一个组件若调用两次,也会得到两份局部值。
这种灵活性来自 Vue 响应式系统与组件层的解耦。是否共享,主要取决于对象在何处创建,以及引用如何传递,而不取决于函数名字是否以 use 开头。只在一个组件内使用的临时输入、展开状态等,没有必要因为存在一个全局 store 就一并移进去。
SSR 不能直接共享每个用户的状态
前面的模块单例适合说明客户端中的共享状态,但服务端渲染需要额外处理。服务器进程可能复用同一模块,多个请求因此可能碰到同一个 store。如果请求 A 把自己的用户数据写入共享对象,请求 B 就可能读到残留内容,形成跨请求状态污染。
应按请求创建应用与状态实例,再通过依赖注入或支持 SSR 的状态管理方案提供给该请求的组件树。不要把当前用户、会话或其他私有数据写入服务端进程级的响应式单例。Vue 的 SSR 文档专门讨论了这一点;本文保留这一边界,但不把小型客户端示例当作完整的 SSR 方案。
什么时候使用 Pinia
少量共享数据,用上面的方式就能说明白。但生产应用还可能需要团队共同遵守的修改约定、Vue DevTools 中的动作时间轴和状态检查、模块热更新(HMR),以及 SSR 支持。Pinia 就是 Vue 官方推荐的状态管理库,提供这些配套能力,并改善 TypeScript 类型推导。
熟悉 Vuex 的读者可以把这理解为官方推荐的演进。Pinia 最初用于探索下一代 Vuex,吸收了团队讨论 Vuex 5 时的许多想法。后来它已经实现了大部分预期功能,Vue 团队因此改为推荐 Pinia。Vuex 进入维护模式,现有应用仍可使用,但不再新增功能;新应用应优先考虑 Pinia。
版本修订,区别于所读中文原文:原页仍写“Pinia 对 Vue 2 和 Vue 3 都可用”。这反映了历史兼容范围,不能据此安装当前任意版本。核对当日,Pinia 当前官方介绍明确说明 Vue 2 支持已于 2025 年取消,并提供 Pinia v2 文档入口。因此,新建 Vue 3 项目应按当前 Pinia 文档选型;维护 Vue 2 项目时,应核对旧版本支持范围和迁移计划。
本篇关注状态的归属、共享与修改路径。它不是 Pinia API 的完整教程,也没有展示复杂持久化、撤销重做、离线同步或并发请求的实现。引入状态库可以提供工具,但不能替代这些业务规则的设计。
来源、修改与许可
原文:Vue.js 官方中文文档《状态管理》。版权声明:Copyright (c) 2019-present, Yuxi (Evan) You and Vue documentation contributors;中文内容归属相应翻译贡献者。中文文档仓库许可规定除图片文件外的内容采用 CC BY 4.0,素材按原样提供,不作担保;原图另按各自权利人的条款处理。
本稿对官方中文全文作结构与措辞整理,保留完整核心示例,补充静态审核意见,并明确更新 Pinia 的 Vue 2 兼容说明。本稿采用新绘图,不复用许可独立的原图。本稿对该文档的整理改编沿用 CC BY 4.0。文中未执行任何代码,也未声明功能测试通过。












暂无评论内容