🎰

有限オートマトン (FSM) JavaScriptで実装

2024/09/11に公開

有限オートマトン(Finite State Machine, FSM)は、コンピュータサイエンスの基本概念であり、限られた数の状態とそれらの状態間の遷移を持つシステムをモデル化するための構造化された方法です。FSMは、ユーザーインターフェイスの管理、ゲームのメカニクス、フォーム送信の処理などで特に有用です。このブログでは、FSMの基本、JavaScriptでの実装方法、そして状態遷移時にロジックを実行するためのアクションをどのように追加するかを探っていきます。

有限オートマトン(FSM)とは?

FSMは次の要素で構成される計算モデルです:

  • 状態: システムが存在できる異なる条件やモード。例えば、交通信号システムでは、状態は 黄色 となります。
  • 遷移: システムがある状態から別の状態へと移行するルール。遷移はイベントやトリガーに基づいて発生します。
  • イベント: FSMが一つの状態から別の状態へ移行するきっかけとなるトリガー。例えば、フォーム送信やボタンクリックです。
  • アクション: 状態遷移中または遷移後に実行されるオプションのロジックや動作。例えば、UIコンポーネントの更新やログの記録などです。

機械は常に特定の状態から始まり、イベントやルールに基づいて他の限られた数の状態に遷移します。FSMは、システムが常に一つの明確な状態にあることを保証するため、システムの動作管理を簡素化するのに非常に役立ちます。

FSMを使用する場面

有限オートマトンは以下のような場面に最適です:

  • UIコンポーネント: ボタン、モーダル、フォームの状態管理(例:待機中ロード中成功エラー)。
  • ゲーム開発: プレイヤーキャラクターの状態管理(例:歩いているジャンプしている落下している)。
  • ワークフローの自動化: タスクの異なるステージの管理(例:保留中承認済み却下)。
  • ネットワーキング: ネットワーク通信の段階管理(例:接続中接続完了切断)。

JavaScriptでの基本的なFSMの実装

まずは、ユーザーログインフォームの状態を管理する簡単な有限オートマトンの例を見てみましょう。状態は idle(待機中)、loading(ロード中)、success(成功)、error(エラー) です。

ステップ1: 状態と遷移を定義

const states = {
    IDLE: 'idle',
    LOADING: 'loading',
    SUCCESS: 'success',
    ERROR: 'error',
};

const transitions = {
    idle: {
        submit: 'loading',
    },
    loading: {
        success: 'success',
        failure: 'error',
    },
    success: {
        reset: 'idle',
    },
    error: {
        reset: 'idle',
    }
};

ここでは、4つの状態(idleloadingsuccesserror)を定義し、これらの状態間の遷移をイベントに基づいて指定しています。

ステップ2: 状態機械を作成

class StateMachine {
    constructor(initialState, transitions) {
        this.state = initialState;
        this.transitions = transitions;
    }

    dispatch(event) {
        const nextState = this.transitions[this.state][event];
        if (nextState) {
            console.log(`状態が ${this.state} から ${nextState} に遷移しています`);
            this.state = nextState;
        } else {
            console.error(`イベント ${event} での状態 ${this.state} からの無効な遷移`);
        }
    }

    getState() {
        return this.state;
    }
}

const fsm = new StateMachine(states.IDLE, transitions);

この StateMachine クラスは、初期状態と遷移を持つFSMを初期化します。dispatch メソッドはイベントに基づいて状態遷移を処理します。

ステップ3: イベントをディスパッチして状態を遷移

fsm.dispatch('submit'); // idle -> loading
console.log(fsm.getState()); // output: loading

fsm.dispatch('success'); // loading -> success
console.log(fsm.getState()); // output: success

fsm.dispatch('reset'); // success -> idle
console.log(fsm.getState()); // output: idle

fsm.dispatch('submit'); // idle -> loading
fsm.dispatch('failure'); // loading -> error
console.log(fsm.getState()); // output: error

この例では、FSMの基本的な状態遷移が示されています。しかし、状態遷移中にロジック(例えば、ロード中のスピナー表示や成功メッセージの表示)を追加したい場合はどうすればよいでしょうか?

FSMにアクションを追加

アクションは、FSMが状態を遷移する際や特定の状態に入ったときに実行されるロジックです。これにより、UIの更新やログの記録、API呼び出しなどの副作用をトリガーすることができます。

では、前述のFSMを拡張し、各状態にアクションを追加します。ここでは、交通信号システム(黄色)をシミュレートし、状態が変わるたびにアクション(UIの更新やログ出力など)を実行します。

ステップ1: 状態、遷移、およびアクションを定義

const states = {
    RED: 'red',
    YELLOW: 'yellow',
    GREEN: 'green',
};

const transitions = {
    red: {
        change: 'green',
    },
    green: {
        change: 'yellow',
    },
    yellow: {
        change: 'red',
    }
};

// 特定の状態に入る際に実行されるアクションを定義
const actions = {
    red: () => {
        console.log("信号は赤です。止まってください!");
        // UI更新などの追加ロジック
    },
    green: () => {
        console.log("信号は緑です。進んでください!");
        // UI更新などの追加ロジック
    },
    yellow: () => {
        console.log("信号は黄色です。減速してください!");
        // UI更新などの追加ロジック
    }
};

各状態(redyellowgreen)に関連するアクションを定義し、状態に入るたびにそのアクションが実行されます。

ステップ2: 状態機械にアクションをサポートさせる

class StateMachine {
    constructor(initialState, transitions, actions) {
        this.state = initialState;
        this.transitions = transitions;
        this.actions = actions;

        // 初期状態のアクションを実行
        this.runAction(this.state);
    }

    dispatch(event) {
        const nextState = this.transitions[this.state][event];
        if (nextState) {
            console.log(`状態が ${this.state} から ${nextState} に遷移しています`);
            this.state = nextState;
            this.runAction(nextState);
        } else {
            console.error(`イベント ${event} での状態 ${this.state} からの無効な遷移`);
        }
    }

    runAction(state) {
        if (this.actions[state]) {
            this.actions[state]();
        }
    }

    getState() {
        return this.state;
    }
}

const fsm = new StateMachine(states.RED, transitions, actions);

このように、状態機械を修正して、新しい状態に入ったときにアクションが実行されるようにします。runAction メソッドは、現在の状態に関連するアクションをトリガーします。

ステップ3: 状態遷移とアクションの実行をシミュレート

fsm.dispatch('change'); // red -> green
fsm.dispatch('change'); // green -> yellow
fsm.dispatch('change'); // yellow -> red

各状態遷移時に、対応するアクションが実行され、コンソールにメッセージが表示されるとともに、UIコンポーネントの更新やその他のロジックがトリガーされます。

例: FSMとアクションを利用したフォーム

フォーム送信FSMに戻り、今度はアクションを追加して、ローディングスピナーの表示や成功・エラーメッセージの表示、フォームのリセットを行います。

const formStates = {
    IDLE: 'idle',
    LOADING: 'loading',
    SUCCESS: 'success',
    ERROR: 'error',
};

const formTransitions = {
    idle: {
        submit: 'loading',
    },
    loading: {
        success: 'success',
        failure: 'error',
    },
    success: {
        reset: 'idle',
    },
    error: {
        reset: 'idle',
    }
};

const formActions = {
    idle: () => {
        console.log("フォームは待機中です。ユーザー入力を待っています。");
        // ローディングスピナーを非表示、フォームをリセット
    },
    loading: () => {
        console.log("フォームを送信中。ローディングスピナーを表示します。");
        // ローディングスピナーを表示
    },
    success: () => {
        console.log("フォームが正常に送信されました。成功メッセージを表示します。");
        // 成功メッセージを表示
    },
    error: () => {
        console.log("フォーム送信に失敗しました。エラーメッセージを表示します。");
        // エラーメッセージを表示
    }
};

const formFSM = new StateMachine(formStates.IDLE, formTransitions, formActions);

// フォーム送信プロセスをシミュレート
formFSM.dispatch('submit'); // idle -> loading、ローディングスピナーを表示
formFSM.dispatch('success'); // loading -> success、成功メッセージを表示
formFSM.dispatch('reset'); // success -> idle、フォームをリセット

このフォーム送信FSMの例では、状態遷移にアクションを追加することで、UIをリアルタイムに更新し、ユーザー体験を向上させています。

まとめ

有限オートマトン(FSM)は、JavaScriptアプリケーションの状態を管理するための明確で構造化された方法を提供します。状態遷移にアクションを追加することで、FSMはさらに強力になり、複雑なワークフローやUIの更新、または副作用を伴う処理を効果的に処理することができます。

この記事では、基本的なFSMの実装方法から、アクションを追加してFSMを強化する方法、さらに交通信号システムやフォーム送信プロセスへの応用までを見てきました。UIコンポーネントの管理や複雑なプロセスのモデリングにFSMを活用することで、予測可能でメンテナンスしやすいシステムを設計できます。

ぜひ、FSMを活用して、あなたのプロジェクトを一段上のレベルに引き上げましょう!

さらなる学びとライブラリ

  • XState - JavaScriptおよびTypeScript向けの強力な状態機械およびステートチャートライブラリ。
  • The Basics of State Machines - JavaScriptにおける状態機械に関するMozilla Developer Networkのガイド。
SocialPLUS Tech Blog

Discussion