一つの画面の中でちょっとしたステートマシンを動かしたいときがあり、ライブラリとか使いたくなかったので簡単に書いてみた。意外とスッキリかけるのでは?
型ガードが三項演算子でも動作するようになってて便利。
type StateContext = { memo: string; } type StateVariant = "todo" | "doing" | "done" | "cancel"; type State = { status: StateVariant; } const EventVariantList = ["ready", "start", "done", "cancel", "other"] as const; const checkEventVariant = (command: string) :command is EventVariant => EventVariantList.includes(command as any); const parseEventVariant = (command: string) :EventVariant => checkEventVariant(command) ? command : "other"; type EventVariant = typeof EventVariantList[number] type Event = { message: string; event: EventVariant; } const RuleSet :{current: StateVariant, event: EventVariant, next:StateVariant, action?: (context: StateContext, event: Event) => StateContext}[] = [ {current: "todo", event: "start", next: "doing"}, {current: "doing", event: "done", next: "done"}, {current: "doing", event: "cancel", next: "cancel"}, {current: "done", event: "ready", next: "todo"}, {current: "cancel", event: "ready", next: "todo"}, ] const updateState = (state: State, context: StateContext, event: Event): {state: State, context: StateContext} => { const logic = RuleSet.find(l => l.current === state.status && l.event === event.event); if (!logic) return { state, context: {...context, memo: "skip" } }; const nextState = logic.next; const nextContext = logic.action ? logic.action(context, event) : { ...context, memo: `updated ${state.status} -> ${nextState} by ${event.message}` } return { state: { ...state, status: nextState }, context: nextContext, }; } function useStateMachine(initialState: State, initialContext: StateContext) { const [state, setState] = useState(initialState); const [context, setContext] = useState(initialContext); const pushState = (event: Event) => { const { state: nextState, context: nextContext } = updateState(state, context, event); setState(nextState); setContext(nextContext); } return { state, context, pushState }; }