xstate-v5 について
xstate-v5は、XState v5のステートマシンやアクターシステムをTypeScriptで設計、実装、レビュー、移行するためのコーディングエージェント向けスキルです。XStateと@xstate/storeの選択や、既存コードに合わせた進め方も扱います。
主な機能
- 01XState v5のステートマシンとアクター設計
- 02XStateと@xstate/storeの選択支援
- 03TypeScript実装とコードレビュー
- 04v4からv5への段階的な移行
- 05React、Vue、Svelte、Solidとの連携
活用シーン
- 01状態と遷移を含むワークフローのモデル化
- 02XStateコードベースのレビューや修正
- 03旧来のv4パターンのv5への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ statelyai/ skills --skill xstate-v5インストール後、Claude Code で/xstate-v5を入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonextract_and_typecheck.pyregrade_weighted.pyreferences/adapters.mdadvanced-patterns.mdexamples.mdobservables-and-inspection.mdv4-to-v5-quick-ref.md詳細
XState v5
このスキルは、まずステートマシンとステートチャートの設計に、次に API の正確性に使用してください。
このスキルはv5 専用です。サンプル、ブログ記事、回答、またはローカルコードに v4 らしさがある場合は、バージョンを混在させずに v5 に置き換えてください。一般的な知識よりも、ローカルリポジトリのコードと公式 v5 ドキュメントを優先してください。
担当する作業:
xstateと@xstate/storeのどちらを使うか選択する- まとまりのない要件から、妥当なマシンまたはアクターシステムを設計する
- 一貫したスタイルで最新の XState v5 TypeScript コードを書く
- 既存の XState コードをレビュー、修正、または改善する
- 古い v4 風のパターンが見つかったら移行する
- 問題が単なる
assign(...)とfromPromise(...)で済まない場合に、適切なアクターの種類とアクションの形を選ぶ - 必要に応じてマシンとアクターを
@xstate/react、@xstate/vue、@xstate/svelte、または@xstate/solidに接続する
最初の確認
既存のコードベースでは:
- ローカルの
package.jsonファイルとインポートを確認します。 - 近くにあるマシン、ストア、アクター、アダプターの使用例を読みます。
- どの程度踏み込むべきか判断する前に、新規コード、ローカルの編集、移行作業を区別します。
- 原則として、周囲のスタイルを維持します。
タスクモード
コードを書く前に、モードを選択してください。
- 新規コード:このスキルにある最新の v5 パターンを優先します。
- ローカルの編集:現在のコードに問題がある、混在している、または整理を求められている場合を除き、ローカルの構造と命名を維持します。
- 移行:安全を確保できる最小限の置き換えで v5 に移行します。より広範な標準化を求められていない限り、構造と動作を維持します。
移行やローカルの編集では、正確性の確保に必要な場合、ローカルの複雑さを大幅に軽減する場合、または明示的に求められた場合を除き、setup(...)、名前付き実装、オブジェクト形式のガード/アクション、タグ、アクターへの分解を導入しないでください。
ツールを選ぶ
ドメインが単純なイベントベースの状態管理である場合は、@xstate/store を優先します。
- 調整、オーケストレーション、または明示的なライフサイクルのモデル化が必要な重要な有限モードがない
- 呼び出す非同期プロセスがない
- アクター間の通信がない
- ガード付き遷移、遅延遷移、並列状態、履歴などのステートマシンやステートチャートの概念が必要ない
マシンレベルのオーケストレーションが不要であれば、単純なフェッチやミューテーションのロジックも @xstate/store に適しています。
以下のいずれかに該当する場合は、XState を優先します。
- 動作や UI に影響する有限モードがある
- 非同期ワークフロー、再試行、キャンセル、またはバックグラウンドプロセスがある
- 相互作用する複数のプロセスまたは子アクターがある
- 明示的なガード、遅延、タグ、または遷移ルールが必要
- 状態を更新するだけでなく、ビジネスプロセスの流れをモデル化する必要がある
問題がマシンを使うほど複雑でない場合は、そのことを明確に伝え、@xstate/store を推奨してください。
ワークフロー
要件が曖昧な場合:
- まずマシンの構成案を作成します。
- 重要な状態、イベント、コンテキスト、アクター、タグに名前を付けます。
- 不確実な点やトレードオフを簡潔に示します。
- その後、コードを書きます。
要件が明確な場合は、構成案を簡潔にするか省略し、すぐにコードに進みます。
概念的な純粋さを求めて既存のコードを過剰にリファクタリングしないでください。重要な点でモデルを改善しつつ、大規模な書き換えが正当化されない場合は、動作している構造を維持します。
モデリングに関する質問
コードを記述または修正する前に、以下の質問を確認してください。
- 有限状態は何ですか? モードはコンテキストではなく、状態に含めてください。
- コンテキストに含めるべきものは何ですか? 永続的なデータだけを保持し、導出可能なブール値や重複するモードは含めないでください。
- ドメインイベントは何ですか?
form.submittedやorder.confirmedのように、意味が明確で、ドット区切りの名前を優先してください。 - ガードにするべきものと、独立した分岐状態にするべきものは何ですか?
- 副作用のうち、呼び出すアクターにするべきものと、遷移アクションにするべきものは何ですか?
- 後の状態で使う一時的な受け渡しデータをコンテキストに保存する代わりに、必要なデータをイベントに含められませんか?
fromPromise(...)は本当に適切なアクターですか? それとも、時間の経過とともにイベントを送り返すコールバック/サブスクリプションプロトコルにするべきですか?- コンシューマーにコンテキストからすべてを推測させるのではなく、マシンから周囲のシステムへイベントを送るべきですか?
- 子の関心事は、親をさらに複雑にするのではなく、スポーンまたは呼び出すアクターにするべきですか?
- 親がこうした中間状態を本当に必要としていますか? それとも、子アクターがその内部の詳細を担うべきですか?
'loading'、'error'、'dirty'など、UI 上の意味を示すタグが必要な状態はどれですか?- UI では、ブール値を追加する代わりに
snapshot.matches(...)、タグ、またはsnapshot.can(...)を使用できませんか?
推奨する v5 のパターン
新しいコードでは、ローカルのコードベースにそうしない強い理由がない限り、次のパターンを優先してください。
- TypeScript のみを使用します。
setup({...}).createMachine({...})を優先します。setup(...)で名前付きのactions、guards、actors、delaysを定義します。- 一貫性を保ちやすくなる場合は、省略形よりも
{ target: 'next' }のようなトランジションオブジェクトを優先します。 - 形を一貫させられる場合は、アクションが 1 つだけでも
actionsに配列を使用します。 - 意図を再利用可能にしたり、名前で表現したりする場合は、
{ type: 'track' }や{ type: 'isValid', params: ... }のようなアクションオブジェクトやガードオブジェクトを優先します。 - UI のセマンティクスや、複数箇所にまたがる状態の意味には
tags: []を優先します。 - ドメインに即したイベント名を優先します。すでにローカルの命名規則が確立されている場合は、それに従います。
- 並列状態では、ライフサイクルを変更する対象のリージョンまたはサブステート内にトランジションを保ちます。ソースリージョン全体をリセットする意図がある場合を除き、兄弟リージョンを対象とするリージョンルートのトランジションや、
reenter: trueの使用は避けます。 - 派生可能な値をコンテキストに保存するのではなく、通常の関数を優先します。
- ある状態から次のステップにデータを渡すだけの場合は、一時的なコンテキストの中継よりもイベントのペイロードを優先します。
- 名前付きアクションやガードにデータを渡すには、
eventを直接参照するのではなくparamsを優先します。 assign(...)だけをアクションパターンとして扱わないでください。単純なコンテキスト更新を超える型付きの再利用可能なロジックには、const machineSetup = setup(...)オブジェクトのスコープ内にあるmachineSetup.createAction(...)、machineSetup.enqueueActions(...)、machineSetup.emit(...)などのヘルパーや、動作に適している場合は名前付きアクションオブジェクトを検討してください。- 名前付きの
assign(...)アクションで複数のコンテキストプロパティを更新する場合、すべてのプロパティ更新関数で同じparams型が共有されます。更新するすべてのフィールドに一貫した 1 つの params オブジェクトを使用するか、処理を別々の名前付きアクションに分けてください。1 つの assigner 内で互換性のない params の形を混在させないでください。 eventを調べる必要があり、その型が明確でない場合は、型を絞り込むためにassertEvent(...)を優先します。- より適切なローカルの方法がない場合を除き、例や最終コードでは
as anyなどの緩い型キャストを避けます。 - アクターロジックは意図的に選択します。1 回のリクエストと 1 つの結果には
fromPromise(...)を優先し、サブスクリプション、タイマー、外部コールバック、時間をおいてイベントを送り返す複数イベントのプロトコルにはfromCallback(...)を優先します。 - マシンから周囲のシステムに直接通知する必要がある場合は、
emit(...)を検討します。 - 可能な限り、親状態は大まかな構成に保ちます。追加の中間状態が主に実装の詳細を表す場合は、その内部動作を子アクターに担わせることを優先します。
- 複数のファイルを示す場合は、すべてが正しくつながるようにします。参照するモジュールやコンポーネントに実際の import/export を含めるか、例をより小さくしてください。
イベントの命名規則に一貫性を持たせてください。イベント名の . は有用で意味があり、部分的なイベント記述子や、より明確なドメインのグループ化にも役立ちます。公式ドキュメントのイベントとトランジションおよびassertEvent(...) による TypeScript の型の絞り込みを参照してください。
標準的なコードの形については references/examples.md を、assign(...) を超える型付きアクション、コールバックアクター、イベントの送出、永続化/ハイドレーションを扱うタスクでは references/advanced-patterns.md を、fromObservable(...)、インスペクション、ブラウザーインスペクターのパターンについては references/observables-and-inspection.md を参照してください。
UI 統合
UI の動作は、できるだけマシンに制御させてください。
- 有限モードのチェックには
snapshot.matches(...)を使用します - ローディング、保存中、変更済みなどの意味的なチェックにはタグを使用します
- イベントが現在有効かどうかの判定には
snapshot.can(...)を使用します - マシンの状態を重複して表現する、手動のブール値を大量に並べることは避けます
コンポーネントが小さなローカルアクターを所有する場合は、useMachine(...) で十分なことがよくあります。
アクターを共有する場合、長期間使用する場合、またはパフォーマンスが重要な場合は、次のようにします。
- アクター参照を一度だけ作成または取得します
useSelector(...)で状態の一部を読み取ります- アクターをサブツリー内で共有する場合は、アクターのコンテキスト/プロバイダーヘルパーを使用します
簡潔なアダプターのガイダンスについては、references/adapters.md を参照してください。
レビューと修正のガイダンス
XState のコードをレビューまたは修正する際は、次の点を確認してください。
- 有限モードが状態ではなくコンテキストに格納されている
- 子アクターに分割すべき巨大なマシン
- 無関係なコールバックに隠されたり、assigner に混在したりしている副作用
- 同じマシン内で v4 と v5 の概念が混在している
- ドメイン上の意味ではなく、UI の仕組みを表している状態名やイベント名
matches(...)、タグ、セレクターに置き換えるべき余分なブール値- 問題が単純な場合に
@xstate/storeを使う機会を逃している - 名前付きの
assign(...)アクションで、プロパティの updater が暗黙に異なるparamsの形状を想定している - 並列リージョンのルート遷移が兄弟リージョンを対象にしている、または
reenter: trueを使用している。v5 では、これにより遷移元のリージョンに再入し、初期サブステートにリセットされ、実行中の invoke がキャンセルされることがあります
分割しすぎないようにしてください。アクター境界が有効なのは、明確さ、所有権、または並行性が向上する場合です。分割すること自体を目的にしないでください。
移行
レガシーコードがある場合、ユーザーがより広範な移行を依頼していない限り、段階的かつ局所的に v5 へ移行してください。
一般的な移行対象は次のとおりです。
cond->guardschema->typesservices->actorsinterpret(...)->createActor(...)- 古い関数シグネチャ ->
({ context, event })のような v5 の分割代入引数
移行タスクでは、次のようにします。
- まずローカルな構造を維持します
- 正確性、互換性、または明確さのために必要な部分だけを変更します
- ユーザーが求めていない限り、推奨される標準的なスタイル全体に統一しません
- 非局所的なリファクタリングを選択した場合は、その内容を説明します
- 近くにあったという理由だけで、不要または無効なフックオプションのパターンを残しません
- React の使用法を移行する際は、古い
interpret形式の実装オーバーライドをuseMachine(...)に無理に組み込むのではなく、現在有効なフックの API を優先します - v4 の形式を v5 にコピーする前に、
type: 'parallel'のリージョンルート遷移を確認します。ハンドラーを所有するサブステート/リージョンに移すか、より広範な再入を意図する場合にのみ、親から制御します
移行パターンを手早く確認するには、references/v4-to-v5-quick-ref.md を使用してください。
永続化
永続化またはハイドレーションが関係する場合は、次のようにします。
- コンテキストのみの復元で本当に十分な場合を除き、コンテキストだけでなくスナップショットを永続化します
- マシンの状態を保存するには、
actor.getPersistedSnapshot()を優先します - 利用可能な場合は、
createActor(machine, { snapshot })またはアダプター/プロバイダーのスナップショットオプションを使ったハイドレーションを優先します - 状態の値と一部のコンテキストデータを推測して、手作業で復元することは避けます
具体的な永続化とハイドレーションのパターンについては、references/advanced-patterns.md を参照してください。
テスト
ユーザーからテストについて質問されたり、テストを依頼されたりしていない限り、テストのガイダンスを追加しないでください。
依頼された場合は、簡潔にまとめます。
- 重要な遷移とガードをテストします
- アクターの正常系と失敗系の動作をテストします
- 状態を持つロジックを重複させず、マシンの状態に対する UI をテストします
- 役立つ場合は、モデルベーステストのユーティリティに触れます
リンクが役立つ場合は、XState のテストとグラフ/パス生成のドキュメントを案内してください。
オプションのツール
Stately Studio とインスペクションツールは、設計、デバッグ、コミュニケーションに役立ちますが、必須ではありません。ユーザーがマシンを設計している場合、可視化を求めている場合、またはデバッグ時の可視性を高めたい場合に紹介してください。
- Stately Studio:ビジュアルモデリングとコラボレーションに使用します
- Inspection API:アクターシステムの監視に使用します
- Stately Inspector:実行中のアプリでのビジュアルインスペクションに使用します
出力形式
次の形式を推奨します。
- 要件が曖昧な場合は、簡単なマシンの概要。
- コード。
- 状態、イベント、コンテキスト、アクター、タグについての簡潔な説明。
- 該当する場合のみ、移行に関する注意事項。
一部しか接続されていない大きな例よりも、完成したファイルを少数提示することを優先してください。完全に接続されていない限り、追加のシェルファイルの概要は示さないでください。
今後さらに拡充できる、より標準的な例については、references/examples.md を参照してください。
最終確認
回答する前に、コンパイルを意識して次の点を簡単に確認してください。
- 別のファイルにあるすべての JSX コンポーネントに、実際のインポートがある
- インポートされたシンボルがすべて、提示したファイルから実際にエクスポートされている
- すべての
send(...)とsnapshot.can(...)の呼び出しで、実際のイベントオブジェクトを使用している - 非同期処理には
actors/fromPromiseとinvoke.inputを使用し、レガシーな形式を使用していない - プロンプトや周辺システムの外部ヘルパーを参照する場合、そのシグネチャを宣言するか、例が単独で成り立つだけの型情報を示している
- 移行例では、使われていないローカルコードを残すためだけに、無効な実装オーバーライドオブジェクトをフックに渡していない
- 実行可能であることを意図したコードフェンスに、プレースホルダーのシェルファイル、省略されたインポート、
...、擬似コードが含まれていない
複数ファイルからなる完全な回答が長くなりすぎる場合は、シェルファイルを省き、パターンを正しく示せる最小限のファイル構成にしてください。