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を使って効果的にステートマシンを定義できるようになりました。
