Mermaid 状态图编辑器
状态图展示一个事物可以处于哪些状态,以及什么事件让它在状态之间迁移。当主题是一段生命周期时用它——一笔订单、一份订阅、一份待审文档。判断标志是:你的标签是形容状态的词而不是动作,比如待处理、已发货、已取消。
带终态取消的订单生命周期
状态是订单“是什么”,箭头上的标签是“发生了什么”。注意“已取消”可以从三个状态到达,却不通向任何地方——这种不对称正是状态图能凸显、而流程图会掩盖的东西。
stateDiagram-v2
[*] --> 待处理: 下单
待处理 --> 已支付: 收到付款
待处理 --> 已取消: 客户取消
已支付 --> 已拣货: 仓库拣货
已支付 --> 已退款: 支付被撤销
已拣货 --> 已发货: 承运商揽收
已拣货 --> 已取消: 库存不足
已发货 --> 已签收: 确认收货
已发货 --> 丢失: 14 天无物流记录
已签收 --> [*]
已退款 --> [*]
已取消 --> [*]
丢失 --> 已退款: 理赔通过实例讲解
1. 最小的状态机
`[*]` 既是起始伪状态也是终止伪状态——具体是哪一个,取决于它在箭头的哪一边。
stateDiagram-v2
[*] --> 草稿
草稿 --> 已发布: 发布
已发布 --> [*]2. 给状态起带空格的名字
状态 ID 不能含空格,但 `state "标签" as id` 能让你既有可读文字又有安全的 ID。这相当于流程图里加引号的标签。
stateDiagram-v2
state "等待审核" as review
state "需要修改" as changes
[*] --> review
review --> changes: 审核人提出意见
changes --> review: 作者提交修改
review --> [*]: 审核通过3. 复合状态
一个状态内部可以包含自己的状态机。当某个阶段有值得说明的内部步骤、又不想让它们污染顶层时就用它——这里是“处理中”内部发生的一切。
stateDiagram-v2
[*] --> 排队中
排队中 --> 处理中: 工作进程取走
state 处理中 {
[*] --> 校验
校验 --> 转换: 结构合法
转换 --> 写入: 字段映射完成
写入 --> [*]
}
处理中 --> 成功: 无异常
处理中 --> 失败: 抛出异常
失败 --> 排队中: 重试
成功 --> [*]4. 选择伪状态
`<<choice>>` 表示由条件而非事件决定的分叉。它让判断保持可见,同时不必假装那是一个对象会停留其中的状态。
stateDiagram-v2
state 判定 <<choice>>
[*] --> 已提交
已提交 --> 判定: 执行风险评分
判定 --> 已通过: 分值 < 40
判定 --> 人工复核: 分值 >= 40
人工复核 --> 已通过: 审核人接受
人工复核 --> 已驳回: 审核人拒绝
已通过 --> [*]
已驳回 --> [*]5. 并发区域
单独一行的两个短横线,会把一个复合状态切分成同时活跃的区域。这是状态图能做、而流程图确实做不到的事。
stateDiagram-v2
[*] --> 注册中
state 注册中 {
[*] --> 邮箱未验证
邮箱未验证 --> 邮箱已验证: 点击链接
--
[*] --> 资料为空
资料为空 --> 资料完整: 提交表单
}
注册中 --> 已激活: 两项均完成
已激活 --> [*]状态图语法速查
请用 `stateDiagram-v2` 而不是 `stateDiagram`。两者都能渲染,但 v2 是仍在积极开发的布局引擎,处理复合状态和并发区域好得多。
| 语法 | 含义 |
|---|---|
| stateDiagram-v2 | 开启该图。`stateDiagram` 仍可用,但是旧版布局。 |
| [*] --> A | 初始状态——入口。 |
| A --> [*] | 终止状态。 |
| A --> B | 未命名触发条件的迁移。 |
| A --> B: 事件 | 标注了触发事件的迁移。 |
| state "标签" as id | 可读标签配上不含空格的 ID。 |
| state A { ... } | 包含自身状态机的复合状态。 |
| -- | 在复合状态内部,把它切分成并发区域。 |
| state x <<choice>> | 基于条件的分叉点。 |
| state f <<fork>> / <<join>> | 并行迁移的分叉与汇合。 |
| note right of A: 文字 | 附加备注。也可用 `note left of`。 |
| direction LR | 把状态机改为从左到右排布。 |
状态图的六种问题
均以 Mermaid 11.12.2 复现。前四种会让图画不出来。后两种更麻烦:它们能正常渲染,只是给你的图并不是你写的意思。
你会看到
Parse error,结尾是:got 'INVALID'
原因
状态 ID 里带了连字符。短横线命名很自然——in-progress、pre-approved——但连字符会被当成迁移箭头的开头。
解决办法
ID 用一个词或下划线,把可读文字放进加引号的标签里。
stateDiagram-v2
[*] --> in-progress
in-progress --> DonestateDiagram-v2
state "处理中" as inProgress
[*] --> inProgress
inProgress --> 完成你会看到
复合状态内部报 Parse error
原因
用 `{` 打开的复合状态没有闭合。右花括号必须单独占一行。
解决办法
把块闭合。
stateDiagram-v2
[*] --> 外层
state 外层 {
[*] --> 内层stateDiagram-v2
[*] --> 外层
state 外层 {
[*] --> 内层
}你会看到
Lexical error on line N. Unrecognized text.
原因
并发区域的分隔符横线数量写错了。必须恰好是两个,单独占一行,且位于复合状态内部。三个横线是完全不同的标记。
解决办法
严格使用 `--`。
stateDiagram-v2
state 并发 {
[*] --> A
---
[*] --> B
}stateDiagram-v2
state 并发 {
[*] --> A
--
[*] --> B
}你会看到
Parse error on line 1,结尾是:got 'ID'
原因
写了并不存在的版本后缀。只有 `stateDiagram` 和 `stateDiagram-v2`,没有别的——`-v3` 在第一行就失败。
解决办法
使用 `stateDiagram-v2`。
stateDiagram-v3
[*] --> 草稿stateDiagram-v2
[*] --> 草稿你会看到
图能渲染,但一个状态悄悄变成了好几个方框
原因
状态 ID 里有空格。Mermaid 不会报错,也不会把后面的部分当作描述——它为每一个词各建一个方框。通过读取生成的 state ID 实测:`[*] --> Awaiting review` 会产生 `Awaiting` 和 `review` 两个状态,箭头只连到第一个,另一个孤零零地留在旁边。三个词就是三个方框,图会不声不响地向旁边变宽。描述功能确实存在,但需要冒号——写成 `review: 等待评审`——这个错误正是与它混淆的结果。
解决办法
用 `state "标签" as id` 声明,并始终通过 ID 引用它。
stateDiagram-v2
[*] --> Awaiting review
Awaiting review --> DonestateDiagram-v2
state "等待审核" as review
[*] --> review
review --> 完成你会看到
图能渲染,但选择节点被画成了普通状态
原因
`<<choice>>` 的声明写在了使用它的迁移之后。Mermaid 在第一次提到某个状态时就把它创建出来,之后再补的构造型不会改变已经创建好的东西。
解决办法
把伪状态声明写在引用它的迁移之前。
stateDiagram-v2
[*] --> 判定
判定 --> 已通过
判定 --> 已驳回
state 判定 <<choice>>stateDiagram-v2
state 判定 <<choice>>
[*] --> 判定
判定 --> 已通过
判定 --> 已驳回渲染须知
以下均为针对本站所用 Mermaid 11.12.2 的实测结果。
中文状态名让状态机更紧凑
一个汉字的显示宽度约为拉丁字母的两倍,但表达同样含义所需的字符数通常只有三分之一左右,净效果是中文图更窄。对状态图来说这一点尤其有用,因为状态名往往是“已发货”“待人工复核”这类短语——中文写法几乎总能排进一行,而英文原文经常需要折行,从而把每个状态的高度撑高。把英文状态机翻译过来之后,通常可以去掉当初为了排版而做的缩写。
stateDiagram 和 stateDiagram-v2 都能渲染——这是个陷阱
网上常见的说法是必须用 `stateDiagram-v2`,否则什么都画不出来。在 11.12.2 里这不成立:两个关键字都能正常渲染。区别在布局质量,尤其是复合状态和并发区域,而且用旧写法时不会有任何警告。如果复合状态看起来挤成一团、箭头绕得很怪,先检查开头的关键字,再考虑重写整张图。
高度每个状态约增加 108 像素
3 个状态的 viewBox 约为 52×462,40 个状态是 60×4680。和流程图一样,宽度几乎不动——状态机是向下生长的。当生命周期很长但很浅时,在图里加一行 `direction LR` 是常规解法。
复合状态是独立布局的
复合状态的内部机器会先单独计算尺寸再放置,所以单个较大的复合状态可能把整张图撑得远比状态数量所暗示的更宽。如果某一个框主导了整张图,把它的内容提到顶层、另开一张图来承载,通常比跟布局较劲更好读。
主题只改颜色,从不改布局
同一份源码在浅色和深色主题下的 viewBox 完全一致,所以切换主题不会让状态机重排。
什么时候该换一种图
如果你的标签是动词——校验、发送、重试——那你描述的是流程而不是生命周期,用流程图才诚实。最明显的信号是:你答不上来“处于这个状态的到底是什么东西”。
如果有多个组件各自都有生命周期、而真正有意思的是它们之间的互动,那么每个组件一张状态图、再加一张时序图描述互动,胜过一张巨大的状态机。
而如果每个状态都能通向其他任何状态,无论怎么画都会是一团乱麻。这通常意味着这些“状态”其实不是状态,而是可以自由组合的标志位——那么一张列出合法组合的表格,比任何图都更有说服力。
其他图类型
作者 Dominik Malsch · 最后更新: