0xf

日記だよ

簡単なステートマシン

一つの画面の中でちょっとしたステートマシンを動かしたいときがあり、ライブラリとか使いたくなかったので簡単に書いてみた。意外とスッキリかけるのでは?

型ガードが三項演算子でも動作するようになってて便利。

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 };
}