什么是状态管理
| 理论上来说,每一个 Vue 组件实例都已经在“管理”它自己的响应式状态了。我们以一个简单的计数器组件为例 |
| |
| <script> |
| export default { |
| |
| data() { |
| return { |
| count: 0 |
| } |
| }, |
| |
| methods: { |
| increment() { |
| this.count++ |
| } |
| } |
| } |
| </script> |
| |
| |
| <template>{{ count }}</template> |
| |
| 它是一个独立的单元,由以下几个部分组成: |
| |
| 1、状态:驱动整个应用的数据源; |
| |
| 2、视图:对状态的一种声明式映射; |
| |
| 3、交互:状态根据用户在视图中的输入而作出相应变更的可能方式。 |
| 然而,当我们有多个组件共享一个共同的状态时,就没有这么简单了 |
| |
| 1、多个视图可能都依赖于同一份状态 |
| |
| 2、来自不同视图的交互也可能需要更改同一份状态 |
| |
| 对于情景 1,一个可行的办法是将共享状态“提升”到共同的祖先组件上去,再通过 props 传递下来。然而在深层次的组件树结构中这么做的话,很快就会使得代码变得繁琐冗长。这会导致另一个问题:Prop 逐级透传问题 |
| |
| 对于情景 2,我们经常发现自己会直接通过模板引用获取父/子实例,或者通过触发的事件尝试改变和同步多个状态的副本。但这些模式的健壮性都不甚理想,很容易就会导致代码难以维护 |
| |
| 一个更简单直接的解决方案是抽取出组件间的共享状态,放在一个全局单例中来管理。这样我们的组件树就变成了一个大的“视图”,而任何位置上的组件都可以访问其中的状态或触发动作 |
用响应式 API 做简单状态管理
在选项式 API 中,响应式数据是用 data() 选项声明的。在内部,data() 的返回值对象会通过 reactive() 这个公开的 API 函数转为响应式
如果你有一部分状态需要在多个组件实例间共享,你可以使用 reactive() 来创建一个响应式对象,并将它导入到多个组件中
| |
| import { reactive } from 'vue' |
| |
| export const store = reactive({ |
| count: 0 |
| }) |
| |
| <script> |
| import { store } from './store.js' |
| |
| export default { |
| data() { |
| return { |
| store |
| } |
| } |
| } |
| </script> |
| |
| <template>From A: {{ store.count }}</template> |
| |
| <script> |
| import { store } from './store.js' |
| |
| export default { |
| data() { |
| return { |
| store |
| } |
| } |
| } |
| </script> |
| |
| <template>From B: {{ store.count }}</template> |
| 现在每当 store 对象被更改时,<ComponentA> 与 <ComponentB> 都会自动更新它们的视图。现在我们有了单一的数据源 |
| |
| 然而,这也意味着任意一个导入了 store 的组件都可以随意修改它的状态 |
| |
| <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(),带上了圆括号作为内联表达式调用,因为它并不是组件的方法,并且必须要以正确的 this 上下文来调用 |
| 除了我们这里用到的单个响应式对象作为一个 store 之外,你还可以使用其他响应式 API 例如 ref() 或是 computed(),或是甚至通过一个组合式函数来返回一个全局状态 |
| |
| import { ref } from 'vue' |
| |
| |
| const globalCount = ref(1) |
| |
| export function useCount() { |
| |
| const localCount = ref(1) |
| |
| return { |
| globalCount, |
| localCount |
| } |
| } |
| |
| 事实上,Vue 的响应性系统与组件层是解耦的,这使得它非常灵活 |
Pinia
| 虽然我们的手动状态管理解决方案在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑: |
| |
| 1、更强的团队协作约定 |
| |
| 2、与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试 |
| |
| 3、模块热更新 (HMR) |
| |
| 4、服务端渲染支持 |
| Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。 |
| |
| 现有用户可能对 Vuex 更熟悉,它是 Vue 之前的官方状态管理库。由于 Pinia 在生态系统中能够承担相同的职责且能做得更好,因此 Vuex 现在处于维护模式。它仍然可以工作,但不再接受新的功能。对于新的应用,建议使用 Pinia。 |
| |
| 事实上,Pinia 最初正是为了探索 Vuex 的下一个版本而开发的,因此整合了核心团队关于 Vuex 5 的许多想法。最终,我们意识到 Pinia 已经实现了我们想要在 Vuex 5 中提供的大部分内容,因此决定将其作为新的官方推荐。 |
| |
| 相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现