状态机驱动的编程
嫌长不看版
简单的应用中:用isLoading表示 加载中
也就是用布尔值表示状态
↓
状态多了会有问题:布尔值爆炸,写起来晕
↓
另一种管理状态的办法:Redux/Vuex,集中存放状态值,手动触发变更
↓
但是还不够好,手动触发变更时没有限制(约束)
↓
引入新的编程范式:状态机编程。它:
理论方面:有数学原理——有限状态机FSM、有计算机科学中的演员模型理论
规范方面:W3C的 SCXML标准
工程方面:有开源的XState库,工程上可用
↓
当状态过多时,可能会导致状态爆炸,可以用状态图解决:通过拆分、嵌套,化大为小
↓
状态机驱动的编程,好处大大的:
同一时刻只有一个状态,确定、可控、可预知
副作用明确:在何时何种情况下做何事
一定程度上说:扩展性、迁移性都很好
对测试友好
状态机可以方便地可视化,在动手写代码之前就进行完善的设计
etc
↓
XState库本身已经成熟,只是国内还没怎么见人用,希望能在适合的场景中落地,慢慢沉淀出一些最佳实践吧
我的笔记
用布尔值来控制应用的状态:
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);业务简单的时候没有问题,直观易懂。但是当需要增加新功能时,直接添加新的布尔值,会导致:
布尔值爆炸:N个布尔值会有2 ** N种可能性(状态)
难以顾及所有情况,容易遇到未定义操作导致状态“跑飞”
改进:通过枚举来声明状态,并且设计对应的transition来控制状态更新、sideEffect来计算副作用。
function transition(state, event) {
return machine.states[state].on[event];
}于是就引入了状态机的概念:

定义:有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automation,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
从数学角度看:状态机是一种实际的计算模型,因为有平凡的线性时间、恒定空间的在线算法去进行运算(模拟状态)。在计算机科学中,有限状态机被广泛用于建模应用行为、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。
翻译成大白话就是:状态机具有封闭性、唯一性、确定性等优秀特性,可以让我们轻松掌控业务中的复杂状态,同时避免难以预知的问题发生。
自动机编程(英语:Automata-based programming)是编程范式中的一种,是指程序或其中的部分是以有限状态机(FSM)为模型的程序,有些程序则会用其他型式(也更复杂)的自动机为其模型。
有限状态机编程(英语:FSM-based programming)大致上等同于自动机编程,但有限状态机编程专指以有限状态机为模型的程序。
自动机编程有以下的二项特征:
程序运行的时间中可以清楚划分成数个自动机的步骤(step),每一个步骤即为一个程序区段,有单一的进入点,可以是一个函数或其他程序。若有需要时,程序区段可以再依其状态的不同,划分为子区段。
不同步骤的程序区段只能透过一组清楚标示的变量交换信息,这些变量称为状态(state),使用自动机编程的程序不能用其他不显然可见的方式标示状态,例如区域变量的数值、回传地址、目前程序指针的位置等。因此一程序在任二个不同时间下的差异,只有状态数值的不同,其余都相同。
自动机编程的运行过程是一个由自动机步骤形成的循环。
面向状态机的编程范式,更加贴近函数式编程的思想:
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
状态机的实现本质上是个纯函数,状态之间的变换是纯的;当然业务中不可避免的会有各种副作用,XState的处理方式是约定一些规则,将副作用的执行明确地定义出来,降低问题发生的可能。
状态机可以通过注册活动(Activities)、上下文(context)、订阅状态机服务(Service.subscribe)等方式进行表达副作用。
当然,状态机在状态数量增多时也会出现“状态爆炸”的问题,解决方案则是使用状态图对状态机进行拆分,引入平行状态、嵌套状态(Parallel states、Hierarchies)等概念,化大为小,状态之间的转换仍然是可控的。
XState中又“有限状态”和“无限状态”的概念:
有限状态是指应用中主要的行为路径
无限状态:即context,eg:从后端取回来的一堆数据
对比
Stack Overflow 上 XState 作者的回答:What is an actual difference between redux and a state machine (e.g. xstate)?
对比项
Redux
XState
状态容器
Redux 实际上也是一个状态容器,使用 Action 发送到 Reducers 去更新状态
XState 也是状态容器,但它分离了有限状态(e.g. "loading","success")和无限状态,或者上下文(e.g. items: [...])
Reducer 对比
Redux 并没有限定应该如何定义 Reducers,它们只是通过当前状态和输入的事件返回下一个状态
XState 是“有规则的 Reducers”,定义符合规则的变换器函数来过渡状态
副作用处理
Redux 并没有内置的处理副作用(side-effects)的实现,依托于社区的 redux-thunk、redux-saga
XState 让副作用显示、可声明
序列化能力
Redux 不可以序列化为 JSON 或其他格式
XState 可以序列化为 JSON,或者从 JSON 中生成状态机。因此十分便携和可配置。
状态机
Redux 不是状态机
XState 严格参照 W3C SCXML 标准进行开发
状态限制
Redux 依赖开发者自主限制状态变换
XState 使用状态图定义状态与事件之间的边界
层级架构设计
Redux 鼓励使用单一、全局的原子状态树
XState 鼓励使用演员模型的方式,因此可以层级化设计多个服务实例进行协作
我自己总结
布尔值
Redux
XState
描述
由N个布尔值进行组合,来控制状态
将N个状态值存起来,通过 action 和 reducer 触发变更
预定义的完整状态机,严格控制所有细节
适合操控的状态数量
少量
少量/中等
少量/中等/海量 均可海量状态时需要配合使用状态图(statecharts)进行状态拆分
状态间的变换
无约束
手动
由变换规则控制
开发成本
低
中等
相对较高(学习成本)
鲁棒性
弱
中等
强
调试/测试友好性
差
中等
优
当应用非常简单时,直接用布尔值/枚举,会很直观;
当应用有一定的复杂度,又对行为有比较严格的要求时,推荐使用XState;
当应用有一定的复杂度,但是不要求严格的状态及状态转换时,用Redux即可搞定;
当然,XState也可以渐进式地使用,去管理一个小模块内部的状态(XState本身足够轻量),与其他状态管理模式共存。
实践
游戏开发的场景非常适合使用XState,会在接下来的需求中吃一次螃蟹🦀️
参考
最后更新于
这有帮助吗?