還記得我們在 Day 15 曾經介紹過 Guard 嗎?
今天要來跟大家分享如何在 XState 中使用 Guard 保護狀態轉移
const someMachine = createMachine(machineConfig,extraOptions)
我們之前學過,可以將 Action 的描述放在 createMachine 的第二個參數 extraOptions
-> { actions:{...} }
。
Guard 也一樣可以放在這裡 extraOptions
-> { guards:{...} }
, Guard 常見的是一組回傳 true / false 的 callback function。
先以 XState 官網得例子作為介紹,來看看怎麼使用!
有個處理搜尋的狀態機 分別有「正常(normal)」、「無效輸入(invalid)」、「搜尋中(searching)」、「搜尋失敗(searchError)」
「正常(normal)」跟 「無效輸入(invalid)」 被規劃為 idle 底下的兩個子狀態
可進入搜尋的條件有什麼? 1. 使用者角色權限是 可搜尋 (canSearch) 且 使用者輸入字數必須大於 0
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: {
// ...
}
}
}
);
我們首先在 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 裡面包含
- cond - 我們在 machine config 中,使用 guard function 時,可多帶的一些條件的那個 object
- state - 狀態轉換前,當前狀態機所在的
state
- _event - SCXML event
用 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'
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
}
}
)
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
那個 1.2 裡的 cond
後面的 searchValid
是不是應該用字串啊
喔喔喔對對對~ Nice Catch
感謝大大
New update