XStateのAPI
次に、XStateの主要なAPIを紹介し、それぞれのAPIがどのように使用されるかを解説します。
ステートマシンを定義する - createMechine
XStateの createMachine
は、ステートマシンを定義するための関数です。この関数を使って、ステートマシンの初期状態、状態遷移、イベント、アクションなどを定義します(リスト1)。
import { createMachine } from 'xstate'; const vendingMachine = createMachine({ id: 'vendingMachine', // (1) initial: 'idle', // (2) states: { // (3) ステートと遷移を定義する }, });
(1)の id
はステートマシンの識別子です。複数のステートマシンを扱う場合に、特定のステートマシンを扱うために利用します。ステートマシンを1つしか扱わない場合には省略しても構いませんが、名前がついていたほうがどんなステートマシンなのか意識しやすいので、筆者は常に記述しています。今回は自動販売機のステートマシンを作るので、vendingMachineと命名しました。
(2) initial
は最初の状態(初期ステート)を表すプロパティです。後述の states
に定義した状態のうち、初期状態としてどの状態を使うか宣言します。
(3)の states
は各状態の定義です。どんな状態があるのか、何をすると状態が遷移するのか、状態が遷移する前後に何が起こるのか、などを定義することができます。
静的なステートマシンを動的に実行する - interpret
XStateの interpret
は、ステートマシンを実行するための関数です。createMachine
で定義されたステートマシンを interpret
に渡すことで、ステートマシンを実際に動かすことができます(リスト2)。
import { createMachine, interpret } from 'xstate'; // ステートマシンの定義 const vendingMachine = createMachine({ /* ... */ }); // ステートマシンの実行 const service = interpret(vendingMachine); service.start(); // (1)
(1)の service.start()
を実行することで、ステートマシンが実行中になります。createMachine
ではステートマシンの静的な定義のみで、そのままでは動作しません。interpret
でステートマシンを読み込んだサービスを作ることで、動的に処理を行わせることができます。
Reactのライフサイクルの管理下でステートマシンを実行する - useMachine
次に、useMachine
について解説します。これはXStateのコア機能というわけではありませんが、Reactから利用するためには必須の機能となるため、本連載の性質上、ここで解説します。
useMachine
は、Reactアプリケーションでステートマシンを簡単に統合するためのカスタムフックです。useMachine
を使うことで、ステートマシンの現在のステートやイベントの送信などが容易になります(リスト3)。
import { useMachine } from '@xstate/react'; import { vendingMachine } from './vendingMachine'; // コンポーネントの定義 function VendingMachine() { const [state, send] = useMachine(vendingMachine); // (1) // ステートに応じた表示やイベントの送信を行う }
(1)にステートマシンを読み込ませることで、useMachine
はステートマシンに基づいた状態管理を行います。戻り値の state
は現在の状態を表すオブジェクトで、send
はイベントを発生させるためのメソッドです。Reactコンポーネントから見ると、ボタンクリックなどの操作を起点にして send
でイベントを発生させて、その結果として変化した state
をUIに反映させる、といった使い方になります。
send
は send('INSERT_COIN', item)
のような形で、第一引数に発生させたいイベントの名前を、第二引数にイベントのパラメータを指定できます。
これらのAPIを組み合わせることで、XStateを使ってアプリケーションの状態管理を効果的に行うことができます。
ステートマシンの定義
続いて、XStateを使用してステートマシンを定義する方法について解説します。ステートマシンの定義には、初期ステート、状態遷移、イベント、ガード条件などの要素が含まれます。それぞれどのような情報を定義するのか、見ていきましょう。
初期ステート
リスト1でも言及しましたが、ステートマシンが開始されたときの状態を初期ステートと呼びます。createMachine
関数内で initia
プロパティを使用して初期ステートを設定します。
const vendingMachine = createMachine({ id: 'vendingMachine', initial: 'idle', // (1) states: { // 状態を定義する }, });
状態遷移
状態遷移は、あるステートから別のステートへ移動することを指します。状態遷移は、createMachine
関数内の states
プロパティで定義された各ステートに on
プロパティを追加することで設定します。on
プロパティは、イベント名をキーとし、遷移先のステート名を値とするオブジェクトです(リスト5)。
const vendingMachine = createMachine({ id: 'vendingMachine', initial: 'idle', states: { idle: { description: "初期状態", // (2) on: { // 「INSERT_COIN」イベントで「inserting」ステートへ遷移 INSERT_COIN: 'inserting', // (1) }, }, inserting: { description: "お金を投入中", on: { // 「INSERT_COIN」イベントで「inserting」ステート(自身)へ遷移 INSERT_COIN: "inserting", // 「HAS_ENOUGH_AMOUNT」イベントで「ready」ステートへ遷移 HAS_ENOUGH_AMOUNT: "ready", }, }, // その他のステートを定義 }, });
(1)のように定義することで、イベントと遷移先を表現できます。イベント名は、通常大文字とアンダースコアを使用して表記します(例: INSERT_COIN
)。
また、ステートには(2)のように description
を記載することもできます。読みやすさのために、どのような状態なのかを記載しておくとよいでしょう。
ちなみに、onプロパティは「イベント名: オブジェクト」の形でも定義できます。この場合は、リスト6のように記述します。
// (略) on: { INSERT_COIN: { // 「INSERT_COIN」イベントで「inserting」ステートへ遷移 target: 'inserting', // (1) }, }, // (略)
この記法を用いる場合、(1)のように target
プロパティに遷移先のステート名を指定します。また、アクションやガード条件など、遷移に関連する追加の設定を行うこともできます。
ガード条件
ガード条件は、特定の条件が満たされた場合にのみ状態遷移を許可する機能です。ガード条件は、cond
プロパティを使用して定義されます(リスト7)。
import { createMachine } from 'xstate'; // (2) 投入済みの金額をチェックする const hasEnoughAmount = (context) => { // 投入金額が100円以上になっていればOK return context.amount >= 100; }; const vendingMachine = createMachine({ id: 'vendingMachine', initial: 'idle', context: { amount: 0, // (3) 投入済みの金額 }, states: { idle: { on: { INSERT_COIN: 'inserting', }, }, inserting: { on: { INSERT_COIN: "inserting", HAS_ENOUGH_AMOUNT: { target: 'ready', cond: hasEnoughAmount, // (1) ガード条件を定義 }, }, }, ready: { // hasEnoughAmountがtrueのときだけこのステートになれる }, // その他のステートを定義 }, });
(1)で HAS_ENOUGH_AMOUNT
イベントを発行できる条件( cond
プロパティ)として、hasEnoughAmount
関数を登録しているので、(2)で実装した条件を満たす場合にのみイベントを発行できます。これにより、条件に応じた状態遷移を制御することができます。
cond
の実装には、(3)で定義した context
の値を使用することが多いです。context
はステートマシンの実行中に、どのステートからでも参照・更新が可能なデータの保存場所です。
これで、ステートマシンの定義に関する基本的な知識が身につきました。初期ステート、状態遷移、イベント、ガード条件の設定方法を理解し、XStateを使って効果的にステートマシンを定義できるようになりました。