状态机简介

状态机(State Machine)是编程中经常遇到的一个概念。登录流程、表单提交、订单系统、动画播放,都可以用它来描述。

它的完整名称是 "有限状态机"(Finite State Machine,缩写 FSM),源自自动机理论,是计算机科学里的一个经典模型,编译器、正则表达式引擎、网络协议、游戏开发里都能看到它的身影。

这篇文章想把这几个概念讲清楚:状态机是什么、为什么要用它、代码里怎么写。

一、先从"状态"说起

状态描述的是"某个东西在当前时刻所处的情况",不是动作。

比如一个人:

  • 在睡觉
  • 在上班
  • 在吃饭
  • 在回家路上

这几个词,都是这个人的状态。

再比如一个订单:

  • 待支付
  • 已支付
  • 已发货
  • 已签收
  • 已取消

这些也是状态。

容易混淆的地方是,把「状态」和「动作」弄混。点击按钮、发起请求、提交表单,这些是动作,不是状态。状态是动作发生之后系统停留的位置------比如"请求进行中""提交失败"。

二、状态机是什么

状态机由三部分组成:

  1. 有哪些状态
  2. 什么事件触发变化
  3. 状态之间怎么切换

可以写成一个公式:

状态机 = 状态 + 触发条件 + 转换规则

红绿灯就是一个状态机。它有三个状态:红灯、绿灯、黄灯。转换规则是:

  • 红灯结束,切到绿灯
  • 绿灯结束,切到黄灯
  • 黄灯结束,切到红灯

这就是一个完整的状态机。

三、为什么叫"机"

这里的"机"指的是"机制",不是机器。

状态机强调状态变化是有规则的:

  • 状态是固定的、有限的(这也是"有限状态机"里"有限"二字的来源)
  • 每次变化都需要触发条件
  • 变化路径是确定的,不能随意跳转

所以状态机不是在说"状态发生了变化",而是在说:系统会按照一套明确的规则,在有限的几个状态之间切换。

四、生活里的状态机

1. 自动售货机

它可能有这些状态:

  • 待机
  • 已投币
  • 已选择商品
  • 出货中
  • 退款中

切换过程大概是这样:

css 复制代码
flowchart TD
    A[待机] --> B[已投币]
    B --> C[已选择商品]
    C --> D[出货中]
    B --> E[退款中]
    E --> A
    D --> A

用户投币,状态从"待机"切到"已投币";选了商品,继续切到"已选择商品";钱不够,则根本不能切到"出货中"。

这里有个重要特点:不是任何时候都可以做任何事。在"待机"状态下不能直接"出货",必须先投币、再选商品,才可能走到下一步------当前处于什么状态,决定了下一步能做什么。

2. 电梯

电梯也是状态机,可能有这些状态:静止、上行、下行、开门、关门。

如果电梯正在上行,这时按"开门"通常不会立刻开,因为"上行"状态下不允许直接进入"开门",必须先停下,再开门。

五、代码里的状态机

用红绿灯举例,写成代码大概是这样:

kotlin 复制代码
const trafficLight = {
  state: 'red',
  transitions: {
    red: 'green',
    green: 'yellow',
    yellow: 'red',
  },
  next() {
    this.state = this.transitions[this.state];
    return this.state;
  },
};

trafficLight.next(); // 'green'
trafficLight.next(); // 'yellow'
trafficLight.next(); // 'red'

这段代码是状态机三要素的直接体现:state 是当前状态,transitions 是转换规则,next() 触发一次切换。

真实项目里,状态往往还要携带上下文数据(比如订单号、错误信息),转换也可能需要满足条件(比如余额是否够)、执行副作用(比如发一次请求)。这种情况下通常会引入专门的状态机库,比如前端常用的 XState,或者用 Redux 这类状态管理工具搭配 reducer 实现类似的约束。思路是一致的:明确列出所有状态,明确列出所有合法的转换,不允许其他情况发生。

六、程序里的状态天然存在

程序的很多功能不是"一下子完成"的,而是要经历好几个阶段。

比如登录:未登录 → 登录中 → 已拿到 token → 已加载权限 → 已进入系统。

比如表单提交:未编辑 → 编辑中 → 提交中 → 提交成功 / 提交失败。

比如订单系统:待支付 → 已支付 → 待发货 → 已发货 → 已完成。

只要一个系统存在"阶段",通常就适合用状态机来理解。

七、以登录流程为例

初学者理解登录,容易简化成"调一下登录接口,成功就结束了"。

实际过程更接近这样:

  1. 用户还没登录
  2. 用户输入账号密码
  3. 前端发起登录请求
  4. 后端返回 token
  5. 前端保存 token
  6. 路由守卫识别到用户已登录
  7. 系统拉取权限和菜单
  8. 页面进入后台首页

画出来大概是这样:

css 复制代码
flowchart TD
    A[未登录] --> B[登录中]
    B --> C[拿到 token]
    C --> D[保存 token]
    D --> E[加载权限菜单]
    E --> F[进入后台]

如果某一步失败------token 没存成功、菜单没加载成功、路由守卫没识别出来------系统就不会进入最后的"进入后台"状态。

这时候看到的现象可能是:登录接口明明成功了,但页面没进去;能进首页,但菜单是空的;一刷新又回登录页。这类问题通常不是单个接口的问题,而是状态没有走通

八、状态机和流程图的区别

常见的疑问是:这不就是流程图吗?两者确实相似,但关注点不同。

流程图关注的是"第一步做什么、第二步做什么",描述的是过程。

状态机关注的是"当前处于什么状态、这个状态下允许发生什么、满足什么条件才能切到下一个状态",描述的是系统此刻的身份。

以"提交订单"为例,流程图会说:点击提交 → 校验参数 → 调接口 → 返回结果。状态机会说:当前是"待提交",点击后进入"提交中",成功则变成"已提交",失败则变成"提交失败"。

两者不冲突,但 排查"系统表现为什么不对"时,状态机的视角通常更有效。

九、容易忽略的一点:非法状态切换

不是所有状态都能直接互相切换。

比如订单系统里,"待支付"可以切到"已支付","已支付"可以切到"已发货",但"待支付"通常不能直接切到"已签收"。

代码里如果出现了这种跳转,往往就是逻辑出了问题------可能是漏判断了某个中间状态,也可能是异常分支没处理。这也是状态机思维在排查 bug 时比较实用的地方:先确认当前状态,再确认这次切换是否合法,问题范围会缩小很多。

十、什么时候适合用状态机

下面这些场景比较适合用状态机来想问题:

  • 登录和权限
  • 订单和支付
  • 表单提交
  • 审核流
  • 文件上传
  • 多步骤向导
  • 弹窗、抽屉、动画切换

不过不是所有代码都需要显式建模成状态机。如果一个功能只有一两个状态、没有复杂的分支和中间态,硬套状态机反而会增加不必要的抽象成本。是否值得用,取决于状态的数量和转换规则的复杂度------状态越多、非法转换的代价越高(比如订单、支付这类涉及资金的场景),越值得花时间把它显式定义清楚。

十一、总结

状态机不是什么高深理论,它是一种描述系统的方法:先定义系统可能存在的所有状态,再定义状态之间的合法转换规则。

写代码时,精力容易都花在"过程"上------先做什么、再做什么。但很多复杂问题的根源不在过程,而在状态没想清楚:系统卡在了一个不该停的状态,或者发生了一次不该发生的转换。

遇到这类问题时,多问一句"系统现在处于什么状态",往往比继续追问"程序做了什么"更容易找到答案。

延伸阅读:

相关推荐
陆枫Larry1 小时前
一次登录问题排查:接口没错,错的是旧业务逻辑
前端
小小小小宇1 小时前
大模型打分与采样原理,以及 Pi Agent 核心原理
前端
一位正在转型AI全栈的前端工程师1 小时前
AI 全栈学习之旅 -Week 5:RAG 知识库问答系统:从零到生产级部署的全栈实践总结
前端·python
光影少年2 小时前
react navite 安卓iOS 打包、签名、环境区分
前端·react native·react.js
coderCN2 小时前
Nodejs 第三十四章 数据库(表达式和函数、子查询和连表)
前端·node.js
ssshooter2 小时前
AI 时代你不能不知道的 git worktree
前端·后端·面试
观测云3 小时前
AI时代的用户访问监测:观测云带你身临其境体验用户与前端UI交互旅程
前端·可观测性·观测云·rum
喵本喵叁肆3 小时前
06-M6-部门过滤与综合研判-从问答机到研判助手
前端·javascript·jquery
TomEval4 小时前
【Web UI 自动化】05 - KDT 模式原理与实现
前端·ui·自动化