blog
blog copied to clipboard
2021年再次探讨前端状态管理
我们为什么需要状态管理?
- 数据共享
- 模块化
- 数据更新
- 单向数据流, 单一来源
- 视图响应式
- watch
- computed
我们看看哪些特性是必需的, 缺它不可
- 数据共享是必需品, props 和 $emit 功能只能解决父子组件的数据共享, 数据更新问题, 但实际业务中任何非父子组件都有可能需要数据共享
- 模块化是中型, 大型业务程序的必需品
- 单向数据流, 单一来源, 一般通过 flux 架构来解决, 代价是不能直接改 state, 会让复杂度上升, 只有少数时序敏感的复杂程序才需要这种特性
- 数据更新, 必需品, 数据共享当然不只是读, 也要能写
- 视图响应式, 必需品, 修改数据应该能改变视图, 声明式UI框架的核心功能
- watch, 必需品, 响应系统关键特性之一
- computed, 必需品, 响应系统关键特性之一
因此这里面只有单向数据流不是必需品, 状态管理并不等于数据共享和flux架构的组合, 应该根据业务各取所需
简单 store 模式
Vue 官方文档中的一段话:
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了
Vue 官方提供了简单状态管理起步使用官方文档, https://cn.vuejs.org/v2/guide/state-management.html, 此文档很容易让人以为是 Vuex 的原理科普, 但其实这就是一种剥离 Flux 架构的状态管理的常见方式
简单 store 模式支持数据共享, 视图响应式, 更新状态, watch, computed, 天然支持模块化全部必需特性
优点是可以直接像操作变量一样修改状态, 指哪打哪
Vue通用的单组件文件开发方式让很多人以为 data 和组件是一一对应关系, 或者说data就属于组件
事实上 data 和 视图完全可以一对多, 一对多, 一对多, 重要的事情说三遍, 这是很多人忽略的点, 一对多的data依然保持响应式
此方法模块化非常容易, 一个抽象业务对应一个文件, 可以很容易的把业务剥离, 但仍然可以直接操作状态
此方法也是 MobX 推崇且默认的方法, 渲染代码引用业务 store, 而不是写在同一个文件, 感官上就是分离的
count.js
export default {
state: Vue.observable({
count: 0
}),
increment() {
this.state.count++
},
decrement(state) {
this.state.count--
}
}
ComponentA.vue
import count from '@/count'
export default {
data() {
return {
count: count.state
}
},
methods: {
handleClick() {
count.increment()
}
}
}
使用 $root 共享状态
支持数据共享, 视图响应式, 更新状态, watch, computed 几乎全部必需特性, 需要自行模块化
和简单共享 store 几乎一样, 但使用成本更低, 是最简单的状态共享方案
Vue 官方推荐这种模式仅在小型应用中使用
// main.js
new Vue({
data: {
user: null
}
})
// Component.vue
export default {
watch: {
'$root.user'() {}
}
}
官方文档: https://cn.vuejs.org/v2/guide/components-edge-cases.html
和Vuex一样的单一状态树, 模块化, 可以自己写一些辅助函数便于访问和更新状态
export default {
moduleA: {},
moduleB: {}
}
使用 provider / inject
只可以读, 无法写, 更新状态会直接有警告, provider端改状态, inject端也接收不到
写起来需要两个属性兼顾, 有点繁琐, react 也有这种模式
此模式仅适用于公共方法 method, 而不适用状态数据
https://cn.vuejs.org/v2/api/#provide-inject
使用 Vuex
支持响应式, watch, computed, 模块化, 更新状态, 单向数据流
不能直接修改状态, 需要严格遵守 flux 约定, 单项数据流
支持 time travel, 可以使用 devtool 看状态流动
主要有以下缺点
- 更新状态非常繁琐, 需要给每一种状态更新写一个 mutation, 异步操作要写 mutation + action
- 套了一层 commit 和 mutation 后代码不直观
- Vuex 是单一状态树, 所有状态会集中在一起变成全局, 无法按需引用, 需要用命名空间和辅助函数
Vuex 继承了 flux 的信条, (state, action) => newState
, 截至 Vuex 4.0, 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation, 异步函数更麻烦, 大部分业务并不适合, 杀鸡用牛刀
MobX 默认是可以直接修改 state, 只有开启严格模式才是强制 action 修改 state, 也就是说 Vuex 比 MobX 更严格
Vue 官方的意思是复杂程序适合用 vuex, 但也不完全对
明确适合 flux 架构的有编辑器类app, 协同类app等有明确时序概念的复杂程序, 用户每一步操作是一个 action, 并且可以撤销, 有反向操作, 动作录制等功能, 这样调试起来也很清晰
总结
- 中型大型业务使用 简单 store 模式 / MobX 管理状态
- 小型业务使用 $root模式 管理状态
- 复杂时序程序使用 Vuex / MobX严格模式 / Redux 管理状态