课程库 › TypeScript › 状态机 · 把「乱」写成「图」 › 第 5 课
XState 一瞥与状态机的朋友圈 —— 你早就见过它们
普通课约 40 分钟
本课导读
四课手写下来,你已经拥有一台五脏俱全的状态机。本课抬头看看外面的世界,回答三个问题:工业级的状态机库长什么样(剧透:XState 的配置和你的转移表几乎是双胞胎);什么时候该用库、什么时候手写就够;以及最有惊喜感的第三问——状态机还藏在哪些你早就用过的东西里(React 的 useReducer、正则表达式、TCP 连接……)。学完这课你会获得一种"到处认亲"的眼睛。预计 40 分钟。
说明
前置工具箱:第 3 课的表驱动(XState 配置就是它的豪华版);第 4 课的守卫与工单(对应 XState 的 guards 与 actions)。本课概念多、代码少,是一节"地图课"——目标是知道东西在哪,不是全部走到。
XState:你的转移表,工业包装
XState 是 JS/TS 生态最主流的状态机库。看它定义机器的方式——不用装任何东西,形状本身就说明问题:
initial + states + 每个状态的 on 菜单——这就是你的转移表,加了个正式的户口。守卫和副作用同样有对应位置:XState 里叫 guard(写在转移上的条件)和 actions(进入/退出/转移时执行的动作),概念和上一课一模一样,只是从"自己约定"变成了"框架规定"。
所以学 XState 的正确姿势不是从零学起,而是做概念映射:转移表 → states/on;守卫 → guard;工单 → actions;外壳 → XState 的解释器(interpreter/actor,它替你写了 runEffects 那部分)。
状态图:状态机的进化形态
XState 真正的增量在于它实现的不是简单状态机,而是状态图(statechart,David Harel 1987 年提出)——给状态机加了几件"治大机器"的装备,各用一句话认门:
- 嵌套状态:
paying里面可以再分选择支付方式 / 输密码 / 等银行返回——外面看是一个状态,里面自成一台小机器。治的是"状态数量爆炸" - 并行状态:音乐播放器的"播放/暂停"和"静音/有声"互不干扰、同时在场——两台机器并排跑。治的是"两组独立状态被迫相乘"(否则要写出 playing-muted、paused-unmuted……四个组合状态)
- 历史状态:从设置页返回时,回到"离开前所在的那个子状态"而不是从头开始
- 延时与定时:
loading 超过 10 秒自动转 timeout——时间本身作为事件
这些你都能用手写模拟,但代码量会陡增——状态图特性正是"该用库了"的分界线。另外一个杀手锏值得单独一提:因为机器是数据,所以机器能画图。Stately 编辑器(stately.ai)能把 XState 配置直接渲染成可交互的状态图,也能反过来图形化编辑再导出代码——第 3 课"表驱动的规则能被程序读"的豪华版,产品经理真的能看着图和你讨论转移规则。
用库还是手写:一条分界线
| 场景 | 建议 |
|---|---|
| 两三个状态的组件(开关、折叠面板、请求状态) | 手写。一个判别联合 + 一个 switch,二十行搞定,引库是杀鸡用牛刀 |
| 状态多但转移简单 | 手写表驱动,规则即文档 |
| 出现嵌套/并行/定时需求(多步向导、播放器、通话、长流程) | 上 XState——手写模拟这些特性的代码量已超过学库成本 |
| 团队要可视化讨论、要状态图文档 | 上 XState + Stately |
判断的本质和一切工程选型一样:库解决的是规模问题,不是原理问题。原理你已经全部手写过一遍——这正是这个单元先手写四课再见库的原因:会造轮子的人换库不慌。
到处认亲:状态机的朋友圈
最后是本课最有惊喜感的部分。以下这些东西,内核全是你这四课学的转移函数:
React 的 useReducer。它要求你提供的 reducer 签名是 (state, action) => state——这就是转移函数,action 就是事件,dispatch 就是"发事件"。区别只有一个:reducer 通常不带"状态集合有限"的自觉,随便算新状态。用状态机的纪律写 reducer(state 用判别联合、非法转移返回原状态),你会写出团队里最稳的那批组件。Redux 同理——它的 reducer 概念正是从这套理论借的。
正则表达式引擎。/ab+c/ 匹配字符串时,引擎内部就是一台状态机:每读一个字符就是一个事件,触发状态转移,走到"接受状态"即匹配成功。正则的学名"正则语言"在计算理论里的定义就是有限状态自动机能识别的语言——你写的每个正则,背后都被编译成了一台机器。
网络协议。TCP 连接的一生(LISTEN → SYN_SENT → ESTABLISHED → FIN_WAIT → CLOSED……)是教科书级状态机,RFC 文档里就画着状态图。执行 netstat 命令看到的那一列 ESTABLISHED,就是在读机器的当前状态。
游戏 AI 与编译器。巡逻 → 发现玩家 → 追击 → 丢失目标 → 返回巡逻,是游戏 NPC 的标配写法;编译器把源代码切成 token 的词法分析器,也是一台逐字符吃输入的状态机。
这个清单的意义不是炫博学:"有限的情况 + 明确的转移规则"是计算机科学里反复重演的母题。你在这个单元练的不是一个 TS 技巧,是一种建模眼光——下次面对任何"说不清现在处于什么情况"的系统,你知道第一刀从哪里切。
✍️ 练习
练习 1:概念映射表
不看课文,默写一张两栏映射表:左栏是本单元手写的概念(状态联合、事件联合、转移表、守卫、工单、外壳、穷尽检查),右栏是 XState/生态里的对应物。写完对照课文批改。
✅ 参考答案
状态联合 → states 配置的键;事件联合 → on 菜单的键(XState 事件对象同样带 type 字段);转移表 → states + on 配置;守卫 → guard;工单 → actions;外壳 → 解释器(interpreter/actor);穷尽检查 → XState 的 TS 类型生成(以及你依然可以在自己代码里用 assertNever)。批改标准:对到概念即可,名词记不准没关系——知道"这东西我写过"比背 API 名重要得多。
练习 2:用状态机纪律写一个 reducer
用 useReducer 风格写一个计数器 reducer,但按状态机纪律加规则:状态为 { status: "active"; count: number } 或 { status: "locked"; count: number };事件 INC、DEC、LOCK、UNLOCK。规则:active 时 INC/DEC 生效(count 不得低于 0——守卫)、LOCK 进入 locked;locked 时只认 UNLOCK,其余无视。
✅ 参考答案
function reducer(state: CounterState, event: CounterEvent): CounterState {
switch (state.status) {
case "active":
if (event.type === "INC") {
return { status: "active", count: state.count + 1 };
}
if (event.type === "DEC") {
if (state.count > 0) {
return { status: "active", count: state.count - 1 };
}
return state; // 守卫:不出负数
}
if (event.type === "LOCK") {
return { status: "locked", count: state.count };
}
return state;
case "locked":
if (event.type === "UNLOCK") {
return { status: "active", count: state.count };
}
return state; // locked 只认 UNLOCK
}
}
这个函数原样贴进 React 的 useReducer 就能用——dispatch({ type: "INC" }) 就是发事件。对比普通 reducer,它多了两样:状态用判别联合(locked/active 不可能混淆)、非法操作返回原状态(锁定时按钮乱点无效,UI 层零防御代码)。
📝 随堂测验
1. 学 XState 的推荐姿势是?
2. statechart(状态图)相对基础状态机的增量装备是?
3. '因为机器是数据,所以机器能画图'指的是?
4. useReducer 的 reducer 与本单元转移函数的关系是?
5. '两三个状态的折叠面板'该用 XState 吗?
本课小结
- XState 配置 = 转移表的工业包装:initial/states/on;守卫 → guard、工单 → actions、外壳 → 解释器
- statechart 四装备:嵌套、并行、历史、延时——出现即是"该用库"的信号;Stately 把机器画成图
- 选型口诀:小机器手写、简单规则查表、状态图需求上库
- 到处认亲:useReducer 的 reducer 就是转移函数;正则引擎、TCP、游戏 AI、词法分析全是状态机
- 单元真正教的是建模眼光:"有限的情况 + 明确的转移规则"是反复重演的母题
下一课收官项目:多步表单向导——状态、事件、守卫、工单、穷尽检查全部上场,再加一场"新增状态"的重构演习,亲手体验编译器点名的快感。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…