前端状态机系列: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 组成的。

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
initialfalsenoneIDREFSnone合法的状态规范状态机的初始状态的 id。如果未指定,则默认初始状态是文档顺序中的第一个子状态
namefalsenoneNMTOKENnone任何有效的NMTOKEN此状态机的名称。它纯粹是为了提供信息
xmlnstruenoneURInonehttp://www.w3.org/2005/07/scxml
versiontruenonedecimalnone必须 “1.0”
datamodelfalsenoneNMTOKENplatform-specific“null”, “ecmascript”, “xpath” 或者其他平台定义的值本文档所需的数据模型。 “null”表示 Null 数据模型,“ecmascript”表示 ECMAScript 数据模型,“xpath”表示 XPath 数据模型
bindingfalsenoneenum“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>

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
idfalsenoneIDnone状态 ID
initialfalse不得与 元素一起指定。绝不能以原子状态出现。IDREFSnone此状态的默认初始状态

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>

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
eventfalseEventsTypes.datatypenone以空格分隔的事件描述符列表触发此转换的事件指示符列表
condfalseBoolean expression‘true’布尔表达式转换条件
targetfalseIDREFSnone要跳转到的状态要转换到的状态或并行区域的标识符
typefalseenum“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>

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
idfalsenoneIDnoneXML 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>

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
idfalsenoneIDnoneXML 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>

属性字段描述如下:

名称必填属性约束类型默认值有效值描述
idfalsenoneIDnoneXML Schema 中定义的有效 id状态 ID
typefalsenoneenum“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="新建"

以上是关于前端状态机系列:SCXML与XState对应关系的主要内容,如果未能解决你的问题,请参考以下文章

前端状态机系列:SCXML与XState对应关系

前端状态机:XState 首个中文文档上线了

前端状态机:XState 首个中文文档上线了

前端状态机:XState 首个中文文档上线了

前端状态机:XState 首个中文文档上线了

使用C ++数据模型与Qt SCXML状态机