在进行前端项目开发时,状态管理始终是一个绕不开的话题,Vue 与 React 框架本身提供了一部分能力去解决这个问题。但是在开发大型应用时往往有其他考虑,比如需要更规范更完善的操作日志、集成在开发者工具中的时间旅行能力、服务端渲染等。本文以 Vue 框架为例,介绍 Vuex 与 Pinia 这两种状态管理工具在设计与实现上的区别。
Vue 状态管理
首先,先介绍一下 Vue 框架自身提供的状态管理的方式。
Vue 组件内主要涉及到状态、动作和视图三个组成部分。
在选项式 API 中通过 data
方法返回一个状态对象,通过 methods
方法设置修改状态的动作。
如果使用组合式 API + setup 语法糖,则是通过 reactive
方法生成状态,而动作只需要当做普通函数或者箭头函数进行定义即可。
选项式 API:
<script>
export default {
data() { // 状态 state
return {
count: 0
}
},
methods() { // 动作 action
increment() {
this.count++
}
}
}
</script>
// 视图 view
<template> {{ count }} </template>
组合式 API + setup 语法糖:
<script setup lang="ts">
import { reactive } from 'Vue'
// 状态 state
const state = reactive({
count: 0
})
// 动作 action
const increment = () => {
state.count++
}
</script>
// 视图 view
<template> {{ state.count }} </template>
视图由状态生成,操作可以修改状态。
如果可以将页面的某一部分单独抽离成与外界解耦的状态、视图、动作组成的独立个体,那么 Vue 提供的组件内的状态管理方式已经足够了。
但是开发中经常会遇到这两种情况:
- 多个页面组件依赖于相同的状态。
- 在多个页面组件内的不同交互行为需要修改同一个状态。
比如我们要做一个主题定制功能,需要在项目入口处获取接口中的颜色参数,然后在整个项目的很多页面都要使用到这个数据。
一种方法是使用 CSS 变量,在页面的最顶层的 root 元素上定义一些 CSS 变量,在 Sass 中使用 var()
初始化一个 Sass 变量,所有页面都引用这个变量即可。在项目入口处获取接口数据,需要手动去修改 root 元素上的 css 变量。
在 Vue 中,框架提供了一种 v-bind 的方式去编写 css,我们可以考虑将所有颜色配置存放在一个统一的 store 里面。
遇到这两种情况,通常我们会通过组件间通信的方式解决,比如:
- 对于相邻的父子组件:
props/emit
defineProps({})
defineEmits(['change', '...'])
- 对于多层级嵌套:
provide/inject
provide(name: string | symbol, value: any)
inject(name: string | symbol, defaultValue: any)
1、如果是相邻的父子组件之间通信,可以通过 props+emit 的方式,父组件通过子组件的 props 传入数据,在子组件内部通过 emit 方法触发父组件的一些方法。
2、如果不是直接相邻,而是中间相隔很多层的嵌套关系,那么可以使用 provide+inject 的方式,高层级的组件抛出状态和动作,低层级的组件接收使用数据和触发动作。
如果目标的两个组件并不在同一条组件链上,一种可能的解决方法是「状态提升」。
可以把共同的状态存储在二者的最小公共祖先组件上,然后再通过上述两种方式进行通信。
- 前者:公共祖先组件存储状态,通过 props 逐级传递响应式状态以及其关联的操作到子组件。
- 后者:公共祖先作为提供方,多个后代组件作为注入方获取数据以及操作数据。
后者编写代码更简洁,更不容易出错。
这样已经能够解决大多数场景的问题了,那么在框架之外的状态管理工具,到底能提供哪些与众不同的能力?
Vuex 与 Pinia 核心思想与用法
Flux 架构
Flux 是 Facebook 在构建大型 Web 应用程序时为了解决数据一致性问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构,或者以它为参考原型。
Flux 架构主要有四个组成部分: