iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0
Software Development

From State Machine to XState系列 第 25

Day25 - 保護你的狀態轉移,在 XState 中使用 Guard Transition

還記得我們在 Day 15 曾經介紹過 Guard 嗎?

今天要來跟大家分享如何在 XState 中使用 Guard 保護狀態轉移

const someMachine = createMachine(machineConfig,extraOptions)

我們之前學過,可以將 Action 的描述放在 createMachine 的第二個參數 extraOptions -> { actions:{...} }

Guard 也一樣可以放在這裡 extraOptions -> { guards:{...} }, Guard 常見的是一組回傳 true / false 的 callback function。

先以 XState 官網得例子作為介紹,來看看怎麼使用!

1. 一般的 Guard

有個處理搜尋的狀態機 分別有「正常(normal)」、「無效輸入(invalid)」、「搜尋中(searching)」、「搜尋失敗(searchError)」

  1. 畫面「正常(normal)」
    https://www.a11ymatters.com/assets/images/patterns/search/final-result.jpg
  2. 畫面「無效輸入(invalid)」https://www.a11ymatters.com/assets/images/patterns/search/search-error.jpg
  3. 畫面「搜尋中(searching)」
    https://www.a11ymatters.com/assets/images/patterns/search/search-focus.jpg
    photo by https://www.a11ymatters.com/pattern/accessible-search/

「正常(normal)」跟 「無效輸入(invalid)」 被規劃為 idle 底下的兩個子狀態
可進入搜尋的條件有什麼? 1. 使用者角色權限是 可搜尋 (canSearch) 且 使用者輸入字數必須大於 0

https://ithelp.ithome.com.tw/upload/images/20211009/20130721KBLp4RpPDS.png

1.0 基本 Machine Config

const searchMachine = createMachine(
  {
    id: 'search',
    initial: 'idle',
    context: {
      // 使用者角色權限預設 可搜尋 (canSearch)
      canSearch: true
    },
    states: {
      idle: {
        on: {
          SEARCH: [
            { target: 'searching' },
            { target: '.invalid' } // 轉移到內部同一層的子狀態
          ]
        },
        initial: 'normal',
        states: {
          normal: {...},
          invalid: {...}
        }
      },
      searching: {
        // 進入狀態前,先開始執行 search 的任務(side effect -> executeSearch )
        entry: 'executeSearch'
        // ...
      },
      searchError: {
        // ...
      }
    }
  }
);

1.1 描述你的 Guards

我們首先在 extraOptions 描述 Guard 會怎麼驗證、保護狀態轉移

可轉移的條件有什麼? 1. 使用者角色權限是 可搜尋 (canSearch) 且 使用者輸入字數必須大於 0

const searchMachine = createMachine(machineConfig,
+  {
+    guards: {
+      searchValid: (context, event, guardMeta) => {
+        return context.canSearch && event.query && event.query.length > 0;
+      };
+    }
+  }
)

這裡我們在 searchMachine 第二個參數新增一個 Guard Function,命名 searchValid ,顧名思義這個 callback 是與搜尋狀態有關、該 function 是在驗證使用者輸入符合 1. 使用者角色權限是 可搜尋 (canSearch) 且 使用者輸入字數必須大於 0 。

且有 2 個資訊來做驗證,分別是 context, event,

這邊要跟讀者說聲抱歉,為啥 event object 可以帶 type 以外其他的東西,像是 query 這種,請見 Day 26 在 XState 當中,想將使用者額外的資料帶進去 Machine 裡,也可以一起放在 event object 內

另外還可以取得第三參數 guardMeta,這邊尚未使用到

補充 -

Meta 裡面包含

  1. cond - 我們在 machine config 中,使用 guard function 時,可多帶的一些條件的那個 object
  2. state - 狀態轉換前,當前狀態機所在的 state
  3. _event - SCXML event

1.2 更新你的 Machine Config - 加入 cond

用 cond 來決定是否能轉移狀態!用哪一組 guard function 來驗證狀態轉移~

const searchMachine = createMachine({
    ...
    context: {
      canSearch: true
    },
    ...
    states: {
        on: {
          SEARCH: [
            {
              target: 'searching',
              // 當 searchValid 回傳 true 時(符合搜尋規則時,才能進入 searching)
+             cond: 'searchValid' // or { type: 'searchValid' }
            },
            // 回傳 false 時,進入 invalid 狀態
            { target: '.invalid' }
          ]
        },
    }
    ...
  },
  {
    guards: {
      searchValid: (context, event, { cond }) => {
        return (
          context.canSearch &&
          event.query &&
          event.query.length > 0;
        );
      }
    }
  }
)

如此,我們便能簡單完成的狀態轉移保護如下!

const searchService = interpret(searchMachine)
  .onTransition((state) => console.log(state.value))
  .start();

searchService.send({ type: 'SEARCH', query: '' });
// => 'idle'

searchService.send({ type: 'SEARCH', query: 'something' });
// => 'searching'

2. 變形的 Guard 請注意看 cond

2.1 也可以把 searchValid function 定義在外面

const searchValid = (context, event) => {
  return context.canSearch && event.query && event.query.length > 0;
};
const searchMachine = createMachine({
          SEARCH: [
            {
              target: 'searching',
              // 當 searchValid 回傳 true 時(符合搜尋規則時,才能進入 searching)
-             cond: 'searchValid' // or { type: 'searchValid' }
+             cond: searchValid
            },
            // 回傳 false 時,進入 invalid 狀態
            { target: '.invalid' }
          ]
},
  {
    guards: {
      // 如果實作不變的話,這裡就可以變成選填
      searchValid 
    }
  }
)

2.2 直接把 searchValid 寫在 cond 後面

const searchMachine = createMachine({
          SEARCH: [
            {
              target: 'searching',
              // 當 searchValid 回傳 true 時(符合搜尋規則時,才能進入 searching)
-             cond: 'searchValid' // or { type: 'searchValid' }
+             cond: (context, event) => context.canSearch && event.query && event.query.length > 0
            },
            // 回傳 false 時,進入 invalid 狀態
            { target: '.invalid' }
          ]
},
  {
    guards: {
      // 如果實作不變的話,這裡就變成選填
      searchValid 
    }
  }
)

XState 也提供這樣的方式,讓我們能快速測試原型、驗證想法,但等到開發中後期,還是建議將 Guards Condition Functions ( Predicate callback ) 寫進 const someMachine = createMachine(machineConfig,extraOptions) 的 extraOptions 當中。這樣子我們比較好 除錯、測試以及更漂亮的呈現在 Visualizer 上。

參考資料

https://xstate.js.org/docs/guides/guards.html#guarded-transitions


上一篇
Day24 - 在 XState 中的階層式狀態 Hierarchical States
下一篇
Day26 - 使用 Guard 來實作一個馬克杯的狀態機
系列文
From State Machine to XState31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
DanSnow
iT邦好手 1 級 ‧ 2021-10-11 14:14:45

那個 1.2 裡的 cond 後面的 searchValid 是不是應該用字串啊

Ken Chen iT邦新手 4 級 ‧ 2021-10-11 21:40:20 檢舉

喔喔喔對對對~ Nice Catch
感謝大大

Ken Chen iT邦新手 4 級 ‧ 2021-10-12 02:45:29 檢舉

New update

0
TD
iT邦新手 4 級 ‧ 2021-10-11 22:40:52

加油加油~~~

Ken Chen iT邦新手 4 級 ‧ 2021-10-12 02:45:35 檢舉

New update

我要留言

立即登入留言