日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

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

發布時間:2023/12/9 HTML 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端状态机系列: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="新建"><state id="新建"><initial><transition target="編寫中"></transition></initial><transition target="中斷" event="暫停"></transition><state id="編寫中"><transition target="預覽中" event="下一步"></transition></state><state id="預覽中"><transition target="提交中" event="下一步"></transition></state><state id="提交中"></state><history id="歷史狀態" type="shallow"></history></state><state id="中斷"><transition target="歷史狀態" event="恢復"></transition></state></scxml> Machine({id: "狀態機",initial: "新建",states: {新建: {initial: "編寫中",on: {暫停: "中斷",},states: {編寫中: {on: {下一步: "預覽中",},},預覽中: {on: {下一步: "提交中",},},提交中: {},歷史狀態: {type: "history",},},},中斷: {on: {恢復: "新建.歷史狀態",},},}, });

3.1.8 <onentry>

<onentry>,一個包裝元素,包含進入狀態時要執行的可執行內容。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onentry><log expr="'歡迎進入狀態A'" /></onentry></state> </scxml>

同 <transition>,children 只能包含 可執行內容。

XState 可以直接在 State Node 的 onEntry 進行定義。

示例:

Machine({id: "狀態機",initial: "狀態A",states: {狀態A: {onEntry: actions.log("歡迎進入狀態A"),},}, });

3.1.9 <onexit>

<onexit>,一個包裝元素,包含退出狀態時要執行的可執行內容。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onexit><log expr="'歡迎下次再來狀態A'" /></onexit></state> </scxml>

同 ,children 只能包含 可執行內容。

XState 可以直接在 State Node 的 onExit 進行定義。

示例:

Machine({id: "狀態機",initial: "狀態A",states: {狀態A: {onExit: actions.log("歡迎下次再來狀態A"),},}, });

3.2 可執行內容

可執行內容,只能在 <onentry>、<onexit> 和 <transition> 中使用。它提供了允許 SCXML 會話修改其數據模型并與外部實體交互的鉤子。

不僅包括了 <raise>、<foreach>、<log>、<if>、<elseif>、<else>,還包含了其他分組下的 <script> 、<assign>、<send> 、<cancel>。當然 下面我們還是按照規范文檔中的分類進行對應說明。

在 XState 中,所有的在 SCXML 中的“可執行內容”統稱為 action。所以對應的這些“可執行內容”都在 XState 的 actions 包中。

3.2.1 <raise>

<raise> 元素在當前 SCXML 會話中引發一個事件。可以觸發 <transition> 中的 event。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onentry><raise event="跳轉" /></onentry></state> </scxml>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
eventtrueNMTOKENnone指定事件的名稱。這將與轉換的“event”屬性相匹配。

對應 XState 的 actions.raise 函數。示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><transition target="狀態B" event="跳轉"></transition><onentry><raise event="跳轉" /></onentry></state><state id="狀態B"></state> </scxml> Machine({id: "狀態機",initial: "狀態A",states: {狀態A: {onEntry: actions.raise("跳轉"),on: {跳轉: "狀態B",},},狀態B: {},}, });

3.2.2 <foreach>

<foreach> 元素允許 SCXML 應用程序遍歷 <datamodel> 中的集合,并為集合中的每個項目執行其中包含的 可執行內容。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><datamodel><data expr="[ 10, 20, 30 ]" id="dataArr" /></datamodel><state id="狀態A"><onentry><foreach array="dataArr" index="varIndex" item="varItem"><log expr="varIndex" /><log expr="varItem" /></foreach></onentry></state> </scxml>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
arraytrueValue expressionnone計算結果為可迭代集合的值表達式 <foreach>元素將遍歷此集合的淺表副本
itemtruexsd:stringnone在指定數 <datamodel> 中有效的任何變量名在循環的每次迭代中存儲集合的不同項的變量
indexfalsexsd:stringnone在指定數 <datamodel> 中有效的任何變量名在 foreach 循環的每次迭代中存儲當前迭代索引的變量

children 由一個或多個 可執行內容組成。

可以對應 XState 的 actions.pure 函數,它也可以返回一個或者一組 action,或者什么也不返回。當然這個函數更靈活。示例:

Machine({id: "狀態機",initial: "狀態A",context: {dataArr: [10, 20, 30],},states: {狀態A: {onEntry: actions.pure((context, event) => {const _actions = [];context.dataArr.map((varItem, varIndex) => {_actions.push(actions.log(varIndex.toString()));_actions.push(actions.log(varItem.toString()));});return _actions;}),},}, });

3.2.3 <log>

<log> 允許應用程序生成日志記錄或調試消息。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onentry><log expr="'歡迎進入狀態A'" /></onentry></state> </scxml>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
labelfalsestring空字符串具有依賴于實現的解釋的字符串。它旨在提供有關“expr”指定的日志字符串的元數據。
exprfalse值表達式none返回要記錄的值的表達式

對應 XState 的 actions.log。示例:

Machine({id: "狀態機",initial: "狀態A",states: {狀態A: {onEntry: actions.log("歡迎進入狀態A"),},}, });

3.2.4 <if>、<elseif>、<else>

<if> 是條件執行元素的容器。
<elseif> 是一個空元素,它對 <if> 的內容進行分區,并提供一個判斷是否執行分區的條件。
<else> 是一個空元素,用于劃分 <if> 的內容。它等價于一個帶有“cond”的 <elseif>,它總是計算為真。

<if cond="cond1"><log expr="'cond1==true'" /><elseif cond="cond2" /><log expr="'cond2==true'" /><elseif cond="cond3" /><log expr="'cond3==true'" /><else /><log expr="'其他情況'" /> </if>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
condtrue條件表達式none有效的條件表達式一個布爾表達式

XState 中有很多方法可以實現類似的能力,如果非要對標的話就是 actions.choose 函數了。示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><datamodel><data expr="2" id="value" /></datamodel><state id="狀態A"><onentry><if cond="value == 1"><log expr="'value === 1'" /><elseif cond="value == 2" /><log expr="'value === 2'" /><else /><log expr="'value != 1 && value != 2'" /></if></onentry></state> </scxml> Machine({id: "狀態機",initial: "狀態A",context: {value: 2,},states: {狀態A: {onEntry: actions.choose([{cond: (context, event) => context.value === 1,actions: [actions.log("value === 1")],},{cond: "equal2",actions: [actions.log("value === 2")],},{actions: [actions.log("value != 1 && value != 2")],},]),},},},{guards: {equal2: (context) => context.value === 2,},} );

3.3 數據模型和數據操作

這部分是狀態之外的數據部分的定義和操作。

數據模型通過 <datamodel> 元素定義,該元素包含零個或多個 <data> 元素,每個元素定義一個數據元素并為其分配一個初始值。 這些值可以在線指定或從外部源加載。 然后可以通過 <assign> 元素更新它們。 <donedata>、<content> 和 <param> 元素可用于將數據合并到與外部實體的通信中。 最后,<script> 元素允許結合腳本。

3.3.1 <datamodel>

<datamodel> 是一個包裝器元素,它封裝了任意數量的 <data> 元素,每個元素都定義了一個數據對象。

children 只能包含 <data>。

對應 XState 的頂層 context。

3.3.2 <data>

<data> 元素用于聲明和填充數據模型的部分。

<datamodel><data expr="true" id="VarBool" /><data expr="1" id="VarInt" /><data expr="'這是字符串'" id="VarString" /> </datamodel>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
idtrueIDnone數據項的名稱
srcfalseURInone給出應從中獲取數據對象的位置
exprfalseExpressionnone有效的條件表達式執行以提供數據項的值

在符合標準的 SCXML 文檔中,<data> 元素可以具有“src”或“expr”屬性,但不能 同時具有。此外,如果任一屬性存在,<data> 元素 絕不能有 children。因此,‘src’、‘expr’ 和 children 在 <data> 元素中是互斥的。

在 XState 中直接作為 context 字段的值存在。

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><datamodel><data expr="true" id="VarBool" /><data expr="1" id="VarInt" /><data expr="'這是字符串'" id="VarString" /></datamodel><state id="狀態A"><onentry><log expr="VarBool" /><log expr="VarInt" /><log expr="VarString" /></onentry></state> </scxml> Machine({id: "狀態機",initial: "狀態A",context: {varBool: true,varInt: 1,varString: "這是字符串",},states: {狀態A: {onEntry: [actions.log((context) => context.varBool),actions.log((context) => context.varInt),actions.log((context) => context.varString),],},}, });

3.3.3 <assign>

<assign> 元素用于修改數據模型。

<assign location="Var1" expr="5"/>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
locationtrue路徑表達式none有效的路徑表達式數據模型中要插入新值的位置
exprfalse此屬性不得出現在具有子元素的 <assign> 元素中值表達none有效的值表達式返回要分配的值的表達式

在 XState 中用 actions.assign 函數表示。

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><datamodel><data expr="1" id="VarInt" /><data expr="'這是字符串'" id="VarString" /></datamodel><state id="狀態A"><onentry><assign expr="5" location="VarInt" /><assign expr="'新的字符串'" location="VarString" /></onentry></state> </scxml> Machine({id: "狀態機",initial: "狀態A",context: {varInt: 1,varString: "這是字符串",},states: {狀態A: {onEntry: actions.assign({varInt: 5,varString: "新的字符串",}),},}, });

3.3.4 <script>

<script> 元素將腳本功能添加到狀態機。

<script>console.log('Hello, world!')</script>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
srcfalse如果元素有子元素,則可能不會發生none有效的 URI給出應該下載腳本的位置

<script> 元素的 children 內容表示要執行的腳本代碼。

XState 在很多地方可以表達類似的能力,如 actions 屬性是支持直接賦值函數的,在 actions.log、actions.pure、actions.assign 等函數都可以實現類似能力。

3.3.5 <donedata>

一個包裝元素,保存進入 <final> 狀態時要返回的數據。

<final id="最終狀態"><donedata><param expr="'value1'" name="key1" /><param expr="'value2'" name="key2" /></donedata> </final>

children 可以包含:

  • <content>:可以出現 0 次或 1次。
  • <param>:可以出現 0 次或多次。

一個符合標準的 SCXML 文檔必須指定單個 <content> 元素或一個或多個 <param> 元素作為 <donedata> 的子元素,但不能同時指定兩者。
如果 SCXML 處理器在進入最終狀態時生成“done”事件,它必須執行 <donedata> 元素 <param> 或 <content> 子元素并將結果數據放在 _event.data 字段中。

對應到 XState State Node 的 data 屬性字段。

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A" initial="狀態A1"><transition target="狀態B" event="done.state.狀態A"><log expr="_event.data"></log></transition><final id="狀態A1"><donedata><param expr="1" name="finalCustomeData1" /><param expr="2" name="finalCustomeData2" /></donedata></final></state><state id="狀態B"></state> </scxml> Machine({id: "狀態機",initial: "狀態A",context: {},states: {狀態A: {initial: "狀態A1",onDone: {actions: actions.log((context, event) => {return event.data;}),target: "狀態B",},states: {狀態A1: {type: "final",data: {finalCustomeData1: 1,finalCustomeData2: 2,},},},},狀態B: {},}, });

3.3.6 <param>

<param> 標簽提供了一種識別鍵和動態計算值的通用方法,該值可以傳遞給外部服務或包含在事件中。

<final id="最終狀態"><donedata><param expr="'value1'" name="key1" /><param expr="'value2'" name="key2" /></donedata> </final>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
nametrueNMTOKENnone字符串
exprfalse值表達式none有效值表達式
locationfalse位置表達式none有效位置表達式

類似于一個 key 一個 value 的方式定義事件數據。

XState 比較靈活,直接在事件返回處填寫 Object 即可。

3.3.7 <content>

包含要傳遞給外部服務的數據的容器元素。

<final id="最終狀態"><donedata><content>{key1: 'value1', key2: 'value2'}</content></donedata> </final>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
exprfalse不得與子內容一起出現值表達式none有效值表達式

如果“expr”屬性不存在,處理器必須使用 <content> 的子元素作為輸出。

類似于一個 Object 的方式定義事件數據。功能和 <param> 相似。可以在 <donedata>、<send>、<invoke> 中使用。

XState 比較靈活,直接在事件返回處填寫 Object 即可。

3.4 外部通訊

外部通信功能允許 SCXML 會話從外部實體發送和接收事件,并調用外部服務。

3.4.1 <send>

<send> 用于將事件和數據發送到外部系統,包括外部 SCXML 解釋器,或在當前 SCXML 會話中引發事件。提供“即發即棄”的能力。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onentry><send event="跳轉" /></onentry></state> </scxml>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
eventfalse不得與“eventexpr”一起出現EventType.datatypenone一個字符串,指示正在生成的消息的名稱
eventexprfalse不得與“event”一起出現值表達式none“event”的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <send> 元素時對其進行執行,并將結果視為已作為“event”的值輸入
targetfalse不能與“targetexpr”一起出現URInone有效的目標 URI平臺應將事件發送到的消息目標的唯一標識符
targetexprfalse不能與“target”一起出現值表達式none有效目標 URI 的表達式“target”的動態替代方案。如果存在此屬性,則 SCXML 處理器必須在執行父 <send> 元素時對其進行執行,并將結果視為已作為“target”的值輸入
typefalse不能與“typeexpr”一起出現URInone標識消息傳輸機制的 URI
typeexprfalse不能與“type”一起出現值表達式none“type”的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <send> 元素時對其進行執行,并將結果視為已作為“type”值輸入
idfalse不得與“idlocation”一起出現xml:IDnone要用作此 <send> 實例的標識符的字符串文字
idlocationfalse不得與“id”一起出現位置表達式none任何位置表達式執行為可以存儲系統生成的 id 的數據模型位置
delayfalse不能與“delayexpr”或屬性“target”具有值“_internal”一起出現Duration.datatypenone指示處理器在分派消息之前應等待多長時間
delayexprfalse不得出現在“delay”或屬性“target”的值為“_internal”時值表達式none“delay”的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <send> 元素時對其進行執行,并將結果視為已作為“delay”值輸入
namelistfalse不得與 <content> 元素一起指定位置表達式列表none一個或多個數據模型位置的空格分隔列表,作為屬性/值對包含在消息中。 (位置的名稱是屬性,存儲在位置的值是值。)

children 可以包含:

  • <content>:可以出現 0 次或 1次。
  • <param>:可以出現 0 次或多次。

符合標準的 SCXML 文檔必須準確指定“event”、“eventexpr”和 <content> 之一。符合標準的文檔不得在 <content> 中指定“namelist”或 <param>。

  • SCXML 處理器必須包含 <param> 或 ‘namelist’ 提供的所有屬性和值,即使出現重復也是如此。
  • 如果存在“idlocation”,SCXML 處理器必須在執行父 <send> 元素時生成一個 id 并將其存儲在此位置。
  • 如果通過“delay”或“delayexpr”指定延遲,SCXML 處理器必須將字符串解釋為時間間隔。 它必須僅在延遲間隔過去后才發送消息。 (請注意,發送標記的執行將立即返回。)處理器必須在執行 <send> 元素時執行所有參數到 <send>,而不是在實際發送消息時執行。 如果 參數的執行產生錯誤,處理器必須丟棄該消息而不嘗試傳遞它。 如果 SCXML 會話在延遲間隔過去之前終止,則 SCXML 處理器必須丟棄該消息而不嘗試傳遞它。

對應 XState 的 actions.send 函數。結構類似:

{"event": "scxml.event","options": {"id": "scxml.id","delay": "scxml.delay","to": "scxml.target"} }

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><transition target="狀態B" event="跳轉"></transition><onentry><send event="跳轉" /></onentry></state><state id="狀態B"></state> </scxml> Machine({id: "狀態機",initial: "狀態A",states: {狀態A: {onEntry: actions.send("跳轉"),on: {跳轉: "狀態B",},},狀態B: {},}, });

3.4.2 <cancel>

<cancel> 元素用于取消延遲的 <send> 事件。 SCXML 處理器不得允許 <cancel> 影響未在同一會話中引發的事件。 處理器應盡最大努力取消具有指定 ID 的所有延遲事件。 但是請注意,它不能保證成功,例如,如果事件在 <cancel> 標記執行時已經交付。

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="狀態A"><state id="狀態A"><onentry><cancel sendid="跳轉ID" /></onentry></state> </scxml>

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
sendidfalse不得與 sendideexpr 一起出現IDREFnone延遲事件的 sendid要取消的事件的 ID。如果多個延遲事件有這個 sendid,處理器將全部取消
sendidexprfalse不得與 sendid 一起出現值表達式none計算結果為延遲事件 ID 的任何表達式‘sendid’ 的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <cancel> 元素時對其進行執行,并將結果視為已作為“sendid”的值輸入

對應 XState 的 actions.cancel 函數。

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="未登錄"><state id="未登錄"><transition target="已登錄" event="登錄"></transition></state><state id="已登錄"><transition target="已登錄" event="活動"></transition><onentry><send event="注銷" delay="1000 * 60" target="未登錄" id="消息ID" /></onentry><onexit><cancel sendid="消息ID"></cancel></onexit></state> </scxml> Machine({id: "狀態機",initial: "未登錄",states: {未登錄: {on: {登錄: "已登錄",},},已登錄: {onEntry: actions.send("注銷", {delay: 1000 * 60,id: "消息ID",}),onExit: actions.cancel("消息ID"),on: {注銷: "未登錄",活動: "已登錄",},},}, });

也可以使用 XState 的 after 語法糖來實現:

Machine({id: "狀態機",initial: "未登錄",states: {未登錄: {on: {登錄: "已登錄",},},已登錄: {after: {[1000 * 60]: "未登錄",},on: {活動: "已登錄",},},}, });

3.4.3 <invoke>

<invoke> 元素用于創建外部服務的實例。

<invoke id="ID_SUB" src="sub.scxml"><param expr="3" name="i_ID" /> </invoke>

<invoke> 提供了一種更緊密耦合的通信形式,特別是能夠觸發平臺定義的服務并將數據傳遞給它。 它及其子 <finalize> 在模擬外部服務行為的狀態中很有用。 <invoke> 元素在狀態的 <onentry> 元素之后執行,并導致創建外部服務的實例。 <param> 和 <content> 元素以及 ‘namelist’ 屬性可用于將數據傳遞給服務。
當并行狀態同時調用相同的外部服務時,將啟動外部服務的單獨實例。 它們可以通過與它們相關聯的 id 來區分。 類似地,從外部服務返回的事件中包含的 id 可用于確定哪些事件是對哪些調用的響應。 返回的每個事件將僅由調用它的狀態中的 <finalize> 處理,但該事件隨后會像狀態機接收的任何其他事件一樣被處理。 因此,finalize 代碼可以被認為是在將事件添加到事件隊列之前應用的預處理階段。 請注意,該事件將傳遞給所有并行狀態以檢查轉換。
由于當狀態機離開調用狀態時調用將被取消,因此在將立即退出的狀態下開始調用是沒有意義的。 因此,<invoke> 元素在進入狀態時執行,但僅在檢查無事件轉換和未決內部事件驅動的轉換之后。 如果找到任何此類啟用的轉換,則立即執行該轉換并立即退出該狀態,而不會觸發調用。 因此,只有在狀態機達到穩定配置時才會觸發調用,即在等待外部事件時它將停留在其中的配置。

屬性字段描述如下:

名稱必填屬性約束類型默認值有效值描述
typefalse不能與“typeexpr”屬性一起出現URInone指定外部服務類型的 URI
typeexprfalse不得與“type”屬性一起出現值表達式none計算結果為 URI 的任何值表達式,該 URI 將是 ‘type’ 的有效值“type”的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <invoke> 元素時對其進行執行,并將結果視為已作為“type”的值輸入
srcfalseURInone要傳遞給外部服務的 URI
srcexprfalse值表達式none‘src’ 的動態替代方案。如果此屬性存在,SCXML 處理器必須在執行父 <invoke> 元素時對其進行執行,并將結果視為已作為“src”的值輸入
idfalseIDnone要用作此 <invoke> 實例的標識符的字符串文字
idlocationfalse位置表達式none任何對數據模型位置求值的數據模型表達式
namelistfalse位置表達式列表none要作為屬性/值對傳遞給調用服務的一個或多個數據模型位置的空格分隔列表。 (位置的名稱是屬性,位置存儲的值是值。)
autoforwardfalse布爾值false指示是否將事件轉發到調用的進程的標志

children 可以包含:

  • <content>:可以出現 0 次或 1次。
  • <param>:可以出現 0 次或多次。
  • <finalize>:可以出現 0 次或 1次。

當 autoforward 屬性設置為 true 時,SCXML 處理器必須將它接收到的每個外部事件的精確副本發送到調用的進程。SCXML 處理器必須在將事件從調用會話的外部事件隊列中刪除以進行處理時轉發該事件。

外部服務在處理時可能會返回多個事件。如果 <invoke> 實例中有一個 <finalize> 處理程序創建了生成事件的服務,則 SCXML 處理器必須在從事件隊列中刪除事件以進行處理之前立即執行該 <finalize> 處理程序中的代碼。它絕不能在 <invoke> 的任何其他實例中執行 <finalize> 處理程序。一旦外部服務完成處理,它必須返回一個特殊事件 done.invoke.id 到調用進程的外部事件隊列,其中id是對應 <invoke> 元素的調用ID。外部服務不得 在此完成事件之后生成任何其他事件。

<invoke> 的實現

包括父進程和子進程之間的通信,是特定于平臺的,但是在被調用的進程本身是 SCXML 會話的情況下,以下要求成立:

  • 如果 <invoke> 中的 <param> 元素的 name 與調用會話的頂級數據聲明中的 <data> 元素的 id 匹配,則 SCXML 處理器必須使用 <param > 元素作為相應 <data> 元素的初始值。(頂級數據聲明是包含在 <scxml> 子元素的 <datamodel> 元素中的那些聲明。)(請注意,這意味著在 <data> 元素中指定的任何值都將被忽略。) namelist 類似。如果名稱列表中鍵的值與調用會話的頂級數據模型中的 <data> 元素的 id 匹配, scxml 處理器必須使用鍵的值作為相應 <data> 元素的初始值。如果名稱不匹配,處理器不得 將 <param> 元素或名稱列表鍵/值對的值添加到調用會話的數據模型中。但是,處理器可以通過其他一些特定于平臺的方式使這些值可用。
  • 當被調用的狀態機達到頂級最終狀態時,處理器必須放置事件 done.invoke.id 上調用機,其中所述外部事件隊列 ID 是用于此調用的調用ID。請注意,達到頂級最終狀態對應于機器的正常終止,并且一旦處于此狀態,它就無法生成或處理任何進一步的事件。
  • 如上所述,如果調用狀態機在接收到 done.invoke 之前退出包含調用的狀態。id事件,它取消調用的會話。執行此操作的方法是特定于平臺的。然而,當它被取消時,被調用的會話必須在下一個微步結束時退出。處理器必須為被調用會話中的所有活動狀態執行 處理程序,但它不能 生成 done.invoke.id 事件。一旦取消調用的會話,處理器必須忽略它從該會話接收到的任何事件。特別是它絕不能將它們插入到調用會話的外部事件隊列中。
  • SCXML 處理器必須支持使用 SCXML 事件I/O 處理器在調用會話和被調用會話之間進行通信。處理器可以支持使用其他事件I/O 處理器在調用會話和被調用會話之間進行通信。

對應 XState State Node 的 invoke 屬性。描述如下:

{"id": "","src": "","autoForward": false,"data": {},"onDone": {},"onError": {} }

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="等待中"><state id="等待中"><transition event="done.invoke.子狀態機" target="時間到" /><invoke id="子狀態機" type="http://www.w3.org/TR/scxml/"><content><scxml name="分鐘子狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="等待中" initial="激活中"><state id="激活中"><onentry><send delay="60s" event="結束"></send></onentry><transition target="完成" event="結束"></transition></state><final id="完成"></final></scxml></content></invoke></state><final id="時間到"></final> </scxml> const minuteMachine = Machine({id: "分鐘子狀態機",initial: "激活中",states: {激活中: {after: {60000: { target: "完成" },},},完成: { type: "final" },}, });Machine({id: "狀態機",initial: "等待中",states: {等待中: {invoke: {src: minuteMachine,onDone: "時間到",},},時間到: {type: "final",},}, });

3.4.4 <finalize>

<finalize> 元素使調用會話能夠使用被調用會話返回的事件中包含的數據更新其數據模型。

<finalize> 包含在執行 <invoke> 后外部服務返回事件時執行的可執行內容。 在系統查找與事件匹配的轉換之前應用此內容。 在可執行內容中,系統變量“_event”可用于引用正在處理的事件中包含的數據。在并行狀態的情況下,僅執行原始調用狀態下的finalize代碼。
在調用期間狀態機從被調用組件接收到的任何事件都由 <finalize> 處理程序在選擇轉換之前進行預處理。 finalize> 代碼用于規范化返回數據的形式,并在執行轉換的“event”和“cond”子句之前更新數據模型。
在符合的SCXML文件,在<敲定>的可執行內容不得引發事件或調用外部動作。特別是,<send> 和 <raise> 元素 不得出現。

children 可以包含 可執行內容。

XState 沒有對應的 API,XState 對于處理消息是非常靈活的,所以這一塊能力是內置進去的。

示例:

<scxml name="狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="等待中"><datamodel><data expr="1" id="VarValue" /></datamodel><state id="等待中"><transition event="childToParent" cond="VarValue==2" target="結束" /><invoke id="子狀態機" type="http://www.w3.org/TR/scxml/"><content><scxml name="發送消息到父級子狀態機" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initial="完成" initial="激活中"><final id="完成"><onentry><send target="#_parent" event="childToParent"><param name="aParam" expr="2" /></send></onentry></final></scxml></content><finalize><assign location="VarValue" expr="_event.data.aParam"/></finalize></invoke></state><final id="結束"></final> </scxml>

3.5 未對應 XState API

上面按照規范與 XState 進行了對應。還有一部分是 XState 特色產物。如下:

  • Actor:Actor 模型,一套非常成熟的模型。用來擴展子狀態機。
  • Interpreter:由于 XState 的狀態機是一套純函數編寫,無任何副作用。所以官方提供了一個 Interpreter 用來托管副作用。
  • Model:用來改善開發人員體驗,分離和組織 context 和 event,共享模型。

3.6 對應大圖

整個對應關系,大致如下圖所示:

4. 最后

連續熬了一個多星期的夜,對 SCXML 和 XState 的關系進行了梳理和對齊,最終產出了這篇 4萬 多字的文章。

做為一個 XState 的 “過來人”,這篇從規范到工具對應關系的文章,正是當初那個在入門產生疑惑時的我,最需要的東西。

也希望這篇文章可以幫助入門和使用狀態機及 XState 的同學解除部分疑惑。

? Github 文章地址

總結

以上是生活随笔為你收集整理的前端状态机系列:SCXML与XState对应关系的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。