[note] State Machines
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料:State Machines in JavaScript with XState, v2 @ Frontend Masters
TL;DR
State and Event
interface Typestate<TContext> {
value: StateValue; // 目前的 state
context: TContext;
event: TEvent; // 收到的事件
matches(parentStateValue): boolean; // 是否是屬於某個 parentState 底下的 state
can(event: TEvent): boolean; // 接收到該 event 某會不會有 transition
}
interface EventObject {
type: string;
}
Machine:
const machine = createMachine({
/*...*/
});
// 使用 transition 可以用來檢視從某 state 接收到特定 event 時會轉換成什麼 state
const nextState = machine.transition('playing', {
type: 'PAUSE',
});
Service:
// 使用 service 可以實際操作某個完整的狀態圖
const service = interpret(machine, { devTools: true }).start();
// 取得當前的 state
service.state;
// 使用 send 來轉換狀態
service.send({ type: 'LOADED' });
service.subscribe((state) => {
state.can({ type: 'PLAY' }); // 收到該 event 後會不會有對應的 transition
state.value;
});
Debug
可以使用 inspect 工具,它會在瀏覽器開啟另一個分頁,同步顯示當前的狀態,而且它是雙向的,也就是說:
- 從 App 改變狀態,inspector 的狀態也會改變
- 從 inspector 改變狀態,App 的狀態也會改變
import { interpret, inspect } from '@xstate/inspect';
inspect({
iframe: false,
url: 'https://stately.ai/viz?inspect',
});
const service = interpret(playerMachine, { devTools: true }).start();
Actions
Actions @ XState
Actions 是屬於 "fire-and-forget" 類型的事件(event),也就是 side-effect,事件會導致連帶執行的動作,可以在三個不同的事件點定義:
entry
exit
- transition (do)
Inline:使用 inline function 定義 action
const machine = createMachine({
initial: 'loading',
states: {
loading: {
// entry action
entry: [
{
type: 'loadData',
exec: () => console.log('Entry / Loading data'),
},
],
exit: [() => console.log('Exit / Loading data')],
on: {
LOADED: 'playing',
},
},
playing: {
on: {
PAUSE: {
// transition action
actions: [
{
type: 'pauseAudio',
exec: () => console.log('Pause audio!'),
},
],
target: 'paused',
},
},
},
paused: {
on: {
PLAY: 'playing',
},
},
},
});
提示
雖然如果只有一個 action 的話,在 actions
後可直接放物件,但會建議統一放陣列,方便未來如果有其他 action 要執行的話,會比較好擴充。