xstate について
XState v5を使う開発者向けの実践ガイドです。ステートマシン、@xstate/store、TanStack Queryとの連携に加え、型安全性やReact統合の指針をまとめています。
主な機能
- 01ステートマシンとアクターモデリング
- 02@xstate/storeによるイベント駆動型状態管理
- 03XStateとTanStack Queryの連携パターン
- 04React統合の指針
- 05注意点と検証チェックリスト
活用シーン
- 01複数ステップのアプリケーションフロー構築
- 02軽量なイベント駆動型状態管理
- 03データ取得とUI状態の連携
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pedronauck/ skills --skill xstateインストール後、Claude Code で/xstateを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/machine-patterns.mdquery-patterns.mdstore-patterns.md詳細
XState v5 開発ガイド
このスキルでは、このプロジェクトで XState v5 エコシステムを利用するための包括的なガイドライン、パターン、ベストプラクティスを紹介します。以下が含まれます。
- XState ステートマシン - 複雑なアプリケーションロジックのための有限ステートマシンとアクター
- @xstate/store - 軽量な状態管理のためのシンプルなイベント駆動型ストア
- XState + TanStack Query - データ取得オーケストレーションのための統合パターン
クイックリファレンス
| ユースケース | ツール | リファレンス |
|---|---|---|
| 複雑な状態フロー、ガード、階層状態 | XState ステートマシン | references/machine-patterns.md |
| シンプルなイベント駆動型状態、アトム、元に戻す/やり直し | @xstate/store | references/store-patterns.md |
| 状態オーケストレーションを伴うデータ取得 | XState + TanStack Query | references/query-patterns.md |
基本原則
XState ステートマシン
- 明示的な状態:アプリケーションロジックを、明示的な有限状態とイベントとしてモデル化します
- アクターモデル:複雑なオーケストレーションにはアクターモデルを採用します
- あり得ない状態:ステートチャートを使って、あり得ない状態を排除します
- 宣言的な考え方:状態遷移を宣言的に捉えます
- 可視化:ステートマシンの視覚的な作成とデバッグには Stately Editor を使います
@xstate/store
- イベント駆動型の更新:すべての状態更新にイベントを使います(直接変更はしません)
- ストアをシンプルに保つ:複雑なロジックではなく、データ管理に専念します
- 最適化されたサブスクリプション:セレクターを活用して、効率的に状態へアクセスします
- リアクティブな状態:合成可能なリアクティブ状態にはアトムを使います
- アップグレードパス:複雑さが増したら XState ステートマシンへ移行します
XState + TanStack Query の統合
- オーケストレーションには XState:ワークフローのオーケストレーションには XState ステートマシンを使います
- データには TanStack Query:データ取得とキャッシュには TanStack Query を使います
- 橋渡し役としてのアクター:Query 操作を XState アクターとしてラップします
- ミューテーション内でのキャッシュ更新:クエリーキャッシュの更新はミューテーションコールバック内に保ちます
XState ステートマシン
setup() API を使ったステートマシンの作成
型安全なステートマシンには、必ず setup() API を使います。
import { setup, createActor } from 'xstate';
export enum CounterEventType {
INCREMENT = 'increment',
DECREMENT = 'decrement',
}
const counterMachineSetup = setup({
types: {
context: {} as { count: number },
events: {} as
| { type: CounterEventType.INCREMENT }
| { type: CounterEventType.DECREMENT },
input: {} as { initialCount: number },
},
guards: {
isPositive: ({ context }) => context.count > 0,
},
});
// Define actions OUTSIDE setup using type-bound helpers
const incrementCount = counterMachineSetup.assign({
count: ({ context }) => context.count + 1,
});
export const counterMachine = counterMachineSetup.createMachine({
id: 'counter',
context: ({ input }) => ({ count: input.initialCount }),
initial: 'active',
states: {
active: {
on: {
[CounterEventType.INCREMENT]: {
actions: incrementCount,
},
},
},
},
});
React との統合
コンポーネントスコープのステートマシンには useMachine() を使い、アクションのオーバーライドには .provide() を使います。
import { useMachine } from '@xstate/react';
import { useMemo } from 'react';
function Counter({ onExternalAction }) {
const machine = useMemo(
() =>
counterMachine.provide({
actions: {
logEvent: () => onExternalAction(),
},
}),
[onExternalAction]
);
const [state, send] = useMachine(machine);
return (
<button onClick={() => send({ type: CounterEventType.INCREMENT })}>
Count: {state.context.count}
</button>
);
}
詳細なパターンについて:references/machine-patterns.md を参照してください
@xstate/store
ストアの作成
明確な初期コンテキストとイベントベースの遷移を使ってストアを定義します。
import { createStore } from '@xstate/store';
const userStore = createStore({
context: {
user: null as { id: string; name: string } | null,
isLoading: false,
error: null as string | null,
},
on: {
setUser: (context, event: { user: { id: string; name: string } }) => ({
...context, // Always spread to preserve other properties
user: event.user,
error: null,
}),
setLoading: (context, event: { isLoading: boolean }) => ({
...context,
isLoading: event.isLoading,
}),
},
});
// Use the trigger API for type-safe event sending
userStore.trigger.setUser({ user: { id: '1', name: 'John' } });
React との統合
import { useSelector, useStore } from '@xstate/store/react';
function Counter() {
const store = useStore({
context: { count: 0 },
on: {
increment: (context, event: { by: number }) => ({
...context,
count: context.count + event.by,
}),
},
});
const count = useSelector(store, (state) => state.context.count);
return <button onClick={() => store.trigger.increment({ by: 1 })}>{count}</button>;
}
元に戻す/やり直しのパターン
import { createStore } from '@xstate/store';
import { undoRedo } from '@xstate/store/undo';
const editorStore = createStore(
undoRedo({
context: { text: '' },
on: {
insertText: (context, event: { text: string }) => ({
...context,
text: context.text + event.text,
}),
},
})
);
editorStore.trigger.undo();
editorStore.trigger.redo();
詳細なパターンについて:references/store-patterns.md を参照してください
XState + TanStack Query の統合
QueryObserver によるクエリーのサブスクリプション
リアクティブなサブスクリプションには、QueryObserver とともに fromCallback を使います。
import { fromCallback } from "xstate";
import { QueryObserver, type QueryClient } from "@tanstack/react-query";
const subscribeToQuery = fromCallback(({ input, sendBack }) => {
const { queryClient, entityId } = input as {
queryClient: QueryClient;
entityId: string;
};
const observer = new QueryObserver(queryClient, {
queryKey: ["data", entityId],
queryFn: async () => { /* fetch logic */ },
});
const unsubscribe = observer.subscribe((result) => {
if (result.data) {
sendBack({ type: "REMOTE_UPDATE", data: result.data });
}
});
// Emit current result immediately
const currentResult = observer.getCurrentResult();
if (currentResult.data) {
sendBack({ type: "REMOTE_UPDATE", data: currentResult.data });
}
return () => unsubscribe();
});
useMutation によるミューテーション
useMutation フックを fromPromise アクターとしてラップし、.provide() 経由で渡します。
const machine = useMemo(
() =>
createDataMachine().provide({
actors: {
saveData: fromPromise(async ({ input }) => {
return await saveMutation.mutateAsync(input.data);
}),
},
}),
[saveMutation]
);
詳細なパターンについて:references/query-patterns.md を参照してください
必須パターン(すべてのツール)
- すべてのステートマシンで
setup()を使う - 型推論の精度が向上します - イベントタイプには列挙型を使う - UPPER_SNAKE_CASE 規約に従います
- アクションは
setup()の外で定義する -setup().assign()のような型に紐づいたヘルパーを使います - 連続したアクションには
enqueueActions()を使う - 関連するアクションをまとめます - React との統合には
.provide()を使う - 外部メソッドでアクションをオーバーライドします inputからコンテキストを初期化する - 動的な初期化には関数形式を使います- ストアでは必ず
...contextをスプレッドする - ほかのプロパティを維持します - サブスクリプションをクリーンアップする - コールバックアクターからクリーンアップ関数を返します
各ツールを使う場面
XState ステートマシンを使う場面:
- 特定の遷移を持つ有限状態が必要な場合
- ガードや条件付きの遷移ロジックが必要な場合
- 非同期オーケストレーションのための呼び出しアクターが必要な場合
- 階層状態または並列状態が必要な場合
- 複雑な複数ステップのワークフローを構築する場合
@xstate/store を使用する場合:
- シンプルなグローバル状態管理またはローカル状態管理
- 複雑なフローを伴わない、イベント駆動型の状態更新
- 元に戻す/やり直す機能が必要な場合
- XState への移行手段がある、軽量な代替手段を利用したい場合
XState + TanStack Query を使用する場合:
- データ取得を UI の状態と連携させる必要がある場合
- キャッシュ同期を伴うリアルタイム機能を構築する場合
- サーバー状態を伴う複雑な非同期ワークフローを管理する場合
よくある落とし穴
すべての XState ツール
- ジェネリックパラメーターは使用せず、代わりに
setup()を使用してください setup()の内部でアクションを定義せず、型に対応したヘルパーを使って外部で定義してください- イベント型に文字列リテラルを使用せず、代わりに列挙型を使用してください
- コンテキストを直接変更せず、必ず
assign()を使用するか、新しいオブジェクトを返してください - ガードを副作用のあるものや、不純なものにしないでください
- 非推奨の v4 API(
interpret()、condなど)を使用しないでください - アクターの
.start()を忘れないでください
@xstate/store 固有
- 戻り値では必ず
...contextを展開してください - クリーンアップなしで購読しないでください
- 複雑な状態機械にはストアを使用しないでください(XState に移行してください)
TanStack Query との統合
QueryObserverをコールバックアクターの外部で作成しないでください- マシンのアクション内でクエリーキャッシュを更新しないでください
- ミューテーションフックをアクターに直接渡さず、
fromPromiseでラップしてください - クロージャーから
QueryClientにアクセスせず、コンテキスト経由で渡してください
検証チェックリスト
XState エコシステムに関わるタスクを完了する前に、以下を確認してください。
状態機械の場合
- 型安全性のために、マシンで
setup()API を使用している - イベント型が列挙型を使って定義されている
- 型に対応したヘルパーを使って、アクションが
setup()の外部で定義されている - 関数形式を使って、
inputからコンテキストが初期化されている - ガードが副作用のない純粋関数である
- React コンポーネントで
useMachine()またはuseActorRef()を使用している
@xstate/store の場合
- トランジションの戻り値では、常に
...contextを展開している - 型安全なイベント送信に
triggerAPI を使用している - サブスクリプションを最適化するためにセレクターを作成している
- React コンポーネント内でサブスクリプションをクリーンアップしている
TanStack Query との統合の場合
-
QueryObserverのサブスクリプションをfromCallbackでラップしている - ミューテーションを
fromPromiseでラップし、.provide()経由で提供している - クエリーキャッシュの更新をマシンのアクションではなく、ミューテーションのコールバック内で行っている
- クリーンアップ関数をコールバックアクターから返している
-
QueryClientをマシンの input/コンテキスト経由で渡している
常に実行する項目
- 型チェックを実行する(
pnpm run typecheck) - テストを実行する(
pnpm run test)
詳細なリファレンスドキュメント
パターン、例、詳細なガイダンスについては、以下のリファレンスファイルを参照してください。
references/machine-patterns.md- 階層状態、並列状態、アクター、テスト、v4 からの移行など、XState マシンのパターンを網羅していますreferences/store-patterns.md- セレクター、アトム、エフェクト、元に戻す/やり直す、マシンへの移行タイミングなど、@xstate/store のパターンを詳しく解説していますreferences/query-patterns.md-QueryObserver、ミューテーション、マシン全体の例を含む、TanStack Query 統合パターンを網羅しています