前端状态机系列:SCXML与XState对应关系
Posted 王乐平
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端状态机系列:SCXML与XState对应关系相关的知识,希望对你有一定的参考价值。
1. 前置说明
这次再说明下自己对状态图的看法。状态图虽然有非常多的优势(参考上篇文章),如果你想使用,关于是否对整个旧项目进行全量状态图化,这里给一个适应范围是:项目中复杂的部分进行状态图建模是非常合适的。如果你有精力是可以尝试对整个项目进行状态图化的。
1.1 状态图
再回顾一下什么是状态图。
状态图的前身是状态机(FSM),FSM 使用过程中会暴露一些问题,如:
- 状态爆炸
- 层次表达能力弱
项目复杂起来,到后期 FSM 会很难维护。
针对这些问题,计算机科学家 David Harel 在 1984 年对 FSM 进行了扩展,发明了 状态图(SC)来解决 FSM 中的问题。(论文地址)
SC 不仅仅是更好的可视化了 FSM,而且它是可执行的。现在的大多数状态机工具库,更确切的说应该是状态图工具库。
SC 定义为一个分层有向图(S,T,R,In,Out),比 FSM 多了一个 R(Orthogonal 正交)的概念。
SC 设计了一套非常复杂且非常精确的符号系统,增强了结构层次的表达能力和有向图的连通表达能力。目前也是 UML 的首选控制模型。
1.2 SCXML
SCXML 全称 State Chart XML,用于控制抽象的状态机表示法。
SCXML 是基于上面说的 David Harel 状态图 和 CCXML(Call Control eXtensible Markup Language) 进行扩展的一套规范。
从 2005 年到 2015 年经历 10 年定制的规范,成为 W3C 推荐规范。目前大部分编程语言的状态机工具都是基于此规范实施的。
1.3 XState
XState 是一个前端的状态图工具库,由微软工程师 David Khourshid 开发。目前是前端状态机里面 Star 最多的,本人体验下来感觉也很不错(本人很高兴在此仓库贡献了 14.7k 行 )。下图是 XState Github Star 记录:
2. 组织说明
XState 的文档写的并不是很好懂,很多概念跳来跳去(当然 大多数国外的文档都有这种问题,作者肯定很想表达清楚,但并不容易做),如果读者对状态机没有概念,突如其来的一堆新的概念会让你措手不及,学习曲线剧增,使用上也不知该如何下手。
如果想要对这些概念有更好的认识和组织,那用 SCXML 和 XState 去对照着看,或许是比较合适的。
2.1 SCXML 的组织
主要有以下部分:
- 核心
<scxml>
<state>
<transition>
<initial>
<parallel>
<final>
<history>
<onentry>
<onexit>
- 可执行内容
<raise>
<foreach>
<log>
<if>
<elseif>
<else>
- 数据模型和数据操作
<datamodel>
<data>
<content>
<param>
<donedata>
<script>
<assign>
- 外部通讯
<send>
<cancel>
<invoke>
<finalize>
2.2 XState 的组织
主要有以下部分:
- Machine
- State
- State Node
- Event
- Transition
- Parallel State
- Final State
- History State
- Effects
- Invoke
- Actions
- send
- raise
- respond
- forwardTo
- escalate
- log
- choose
- pure
- assign
- Activities
- Context
- Guard
- Delay
- Interpret
- Identify
- Actor
- Model
3. 对应关系
下面以 SCXML 为主线去做对应描述。
3.1 核心元素
按照 SCXML 的分类,先从核心部分的元素进行对应说明。
3.1.1 <scxml>
<scxml>
,最外层的状态机包裹元素,携带版本信息,状态机是由它的 children 组成的。
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
initial | false | none | IDREFS | none | 合法的状态规范 | 状态机的初始状态的 id。如果未指定,则默认初始状态是文档顺序中的第一个子状态 |
name | false | none | NMTOKEN | none | 任何有效的NMTOKEN | 此状态机的名称。它纯粹是为了提供信息 |
xmlns | true | none | URI | none | http://www.w3.org/2005/07/scxml | |
version | true | none | decimal | none | 必须 “1.0” | |
datamodel | false | none | NMTOKEN | platform-specific | “null”, “ecmascript”, “xpath” 或者其他平台定义的值 | 本文档所需的数据模型。 “null”表示 Null 数据模型,“ecmascript”表示 ECMAScript 数据模型,“xpath”表示 XPath 数据模型 |
binding | false | none | enum | “early” | “early”, “late” | 要使用的数据绑定 |
children 可以包含:
<state>
<parallel>
<final>
<datamodel>
<script>
对应 XState 是 Machine,Machine 的部分属性描述如下(详情):
"id": "",
"initial": "",
"context": ,
"states":
3.1.2 <state>
<state>
,用来描述状态机中的状态。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="状态A"/>
</scxml>
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
id | false | none | ID | none | 状态 ID | |
initial | false | 不得与 元素一起指定。绝不能以原子状态出现。 | IDREFS | none | 此状态的默认初始状态 |
children 可以包含:
<onentry>
<onexit>
<transition>
<initial>
<state>
<parallel>
<final>
<history>
<datamodel>
<invoke>
对应XState 的 State Node。不过 State Node 是一个 SCXML 多个元素组成的一个属性。由 <state>
、<initial>
、<parallel>
、<final>
、<history>
组成。
State Node 的部分属性描述如下(详情):
"id": "",
"states": ,
"invoke": ,
"on": ,
"onEntry": ,
"onExit": ,
"onDone": ,
"always": ,
"after": ,
"tags": [],
"type": ""
示例:
Machine(
id: "状态机",
states:
状态A:
id: "状态A",
,
,
)
3.1.3 <transition>
状态之间进行转换。由事件触发,通过条件判断后进行转换。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="打开">
<transition cond="_event.data==1" event="点击" target="关闭" />
</state>
<state id="关闭" />
</scxml>
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
event | false | EventsTypes.datatype | none | 以空格分隔的事件描述符列表 | 触发此转换的事件指示符列表 | |
cond | false | Boolean expression | ‘true’ | 布尔表达式 | 转换条件 | |
target | false | IDREFS | none | 要跳转到的状态 | 要转换到的状态或并行区域的标识符 | |
type | false | enum | “external” | “internal” “external” | 确定目标状态是来自于内部转换还是外部转换 |
children 可以包含 可执行内容。
对应XState 的 Event、Transition、Guard。部分属性描述如下(详情):
"on":
"": ,
"*": ,
"自定义事件":
"target": "目标状态",
"cond": "条件判断",
"actions": "可执行内容",
"in": "只能从这个状态过来",
"internal": "内部转换",
"meta": ,
"description": ""
示例:
Machine(
id: "状态机",
states:
打开:
on:
点击:
target: "关闭",
cond: (ctx, event) => event.data == 1,
,
,
,
关闭: ,
,
);
3.1.4 <initial>
<initial>
,表示复杂 元素(即包含子 或 元素的元素)的默认初始状态。并不是一个状态,只是一个指向状态的作用。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="打开">
<initial>
<transition target="写入" />
</initial>
<state id="写入" />
<state id="读取" />
</state>
</scxml>
必须和 <transition>
一起使用,进行状态指定。
children 包含 <transition>
。
XState 可以直接在 State Node 的 initail
进行指定实现。
示例:
Machine(
id: "状态机",
states:
打开:
initial: "读取",
states:
读取: ,
写入: ,
,
,
,
);
3.1.5 <parallel>
该元素表示一个状态,其子项并行执行。当父元素处于活动状态时,子项同时处于活动状态。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<parallel id="网盘">
<state id="写入" />
<state id="读取" />
</parallel>
</scxml>
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
id | false | none | ID | none | XML Schema 中定义的有效 id | 状态 ID |
children 可以包含:
<onentry>
<onexit>
<transition>
<state>
<parallel>
<history>
<datamodel>
<invoke>
XState 可以直接在 State Node 的 type: parallel
进行指定实现。
示例:
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="网盘">
<parallel id="网盘">
<state id="上传">
<initial>
<transition target="空闲" />
</initial>
<state id="空闲">
<transition target="上传中" event="开始" />
</state>
<state id="上传中">
<transition target="成功" event="完在" />
</state>
<state id="成功"></state>
</state>
<state id="下载">
<initial>
<transition target="下载.空闲" />
</initial>
<state id="下载.空闲">
<transition target="下载.下载中" event="开始" />
</state>
<state id="下载.下载中">
<transition target="下载.成功" event="完在" />
</state>
<state id="下载.成功"></state>
</state>
</parallel>
</scxml>
Machine(
id: "状态机",
initial: "网盘",
states:
网盘:
type: "parallel",
states:
下载:
initial: "空闲",
states:
空闲:
on:
开始: "下载中",
,
,
下载中:
on:
完成: "成功",
,
,
成功: ,
,
,
上传:
initial: "空闲",
states:
空闲:
on:
开始: "上传中",
,
,
上传中:
on:
完成: "成功",
,
,
成功: ,
,
,
,
,
,
);
3.1.6 <final>
<final>
表示 <scxml>
或复合 <state>
元素的最终状态。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="下载中">
<transition event="完成" target="成功" />
</state>
<final id="成功" />
</scxml>
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
id | false | none | ID | none | XML Schema 中定义的有效 id | 状态 ID |
children 可以包含:
<onentry>
<onexit>
<donedata>
XState 可以直接在 State Node 的 type: final
进行指定实现。
示例:
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<initial>
<transition target="工作" />
</initial>
<state id="工作">
<initial>
<transition target="正在完成任务" />
</initial>
<!-- 子状态为 final 时,父状态触发 don.state 事件 -->
<transition event="done.state.工作" target="工作完成" />
<state id="正在完成任务">
<transition event="完成" target="任务完成" />
</state>
<final id="任务完成"></final>
</state>
<final id="工作完成" />
</scxml>
Machine(
id: "状态机",
initial: "工作",
states:
工作:
initial: "正在完成任务",
states:
正在完成任务:
on:
完成: "任务完成",
,
,
任务完成:
type: "final",
,
,
onDone: "工作完成",
,
工作完成: ,
,
);
3.1.7 <history>
<history>
伪状态允许状态机记住它的状态配置。以 <history>
状态为目标的 <transition>
会将状态机返回到此记录的配置。
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<history id="历史状态" type="shallow">
<transition target="状态A" />
</history>
<state id="状态A"></state>
</scxml>
属性字段描述如下:
名称 | 必填 | 属性约束 | 类型 | 默认值 | 有效值 | 描述 |
---|---|---|---|---|---|---|
id | false | none | ID | none | XML Schema 中定义的有效 id | 状态 ID |
type | false | none | enum | “shallow” | “deep” 或 “shallow” | 确定是记录当前状态的活动原子子状态还是仅记录其直接活动子状态。 |
children 可以包含 <transition>
。
<transition>
‘target’ 指定默认历史配置的转换。 仅发生一次。 在符合标准的 SCXML 文档中,此转换不得包含“cond”或“事件”属性,并且必须指定一个非空“target”。此转换可能包含可执行内容。 如果 ‘type’ 是“shallow”,那么这个 <transition>
的 ‘target’ 必须只包含父状态的直接子级。 否则,它必须只包含父级的后代。
XState 可以直接在 State Node 的 type: history
进行指定实现。多了一些额外属性:
"type": "history",
"history": "shallow",
"target": "默认指定到父状态"
示例:
<scxml name="状态机" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="新建">
<state 前端状态机系列:SCXML与XState对应关系