0Pricing
TypeScript Academy · レッスン

ステートマシンのモデル化

判別可能なユニオンで有限個の状態を表現します。

「ステートマシンのモデル化」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全4レッスンが含まれています。

判別可能なユニオンとしての状態

UIや非同期処理には、idle、loading、success、errorという明確に異なる状態があります。これらを判別可能なユニオンとしてモデル化すると、各状態が必要なデータだけを持つようにできます。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

const s: State = { status: "idle" };
console.log(s.status);

状態ごとのペイロード

dataを持つのはsuccess状態だけで、messageを持つのはerror状態だけです。idleとloadingは余分なデータを持たないため、状態間で古いデータが漏れ出すのを防げます。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: number[] }
  | { status: "error"; message: string };

const ok: State = { status: "success", data: [1, 2, 3] };
console.log(ok.status, "data" in ok ? ok.data : null);

各状態のレンダリング

statusをswitchで処理する関数を使えば、各状態に適した出力をレンダリングできます。メンバー固有のフィールドは、存在する状態でのみ利用できます。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

function view(s: State): string {
  switch (s.status) {
    case "idle": return "Ready";
    case "loading": return "Loading...";
    case "success": return "Got: " + s.data;
    case "error": return "Failed: " + s.message;
  }
}
console.log(view({ status: "success", data: "users" }));

遷移の定義

ステートマシンは、遷移によって状態間を移動します。遷移関数は現在の状態とイベントを受け取り、次の状態を返します。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

function start(s: State): State {
  if (s.status === "idle") return { status: "loading" };
  return s;
}
console.log(start({ status: "idle" }).status);

イベントもモデル化する

イベント自体も判別可能なユニオンにできます。各イベント型が特定の遷移を引き起こします。

type Event =
  | { type: "FETCH" }
  | { type: "RESOLVE"; data: string }
  | { type: "REJECT"; message: string };

const e: Event = { type: "RESOLVE", data: "ok" };
console.log(e.type);

完全な遷移関数

状態のユニオンとイベントのユニオンを、1つのreducerで組み合わせます。両方をswitchで処理することで、すべての遷移を明示的かつ型安全にできます。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };
type Event =
  | { type: "FETCH" }
  | { type: "RESOLVE"; data: string }
  | { type: "REJECT"; message: string };

function reduce(s: State, e: Event): State {
  if (e.type === "FETCH") return { status: "loading" };
  if (e.type === "RESOLVE") return { status: "success", data: e.data };
  return { status: "error", message: e.message };
}
console.log(reduce({ status: "idle" }, { type: "FETCH" }).status);

不正な状態を表現できない

dataとerror messageを同時に持つことはできません。ユニオンの設計によって、矛盾した状態を構造上禁止できます。

type State =
  | { status: "success"; data: string }
  | { status: "error"; message: string };

// const bad: State = { status: "success", data: "x", message: "y" };
// Allowed extra props would be flagged in strict object checks.
console.log("no contradictory states");

無効な遷移を防ぐ

現在の状態に適用できないイベントを無視することで、クラッシュさせずにマシンを一貫した状態に保てます。

type State = { status: "idle" } | { status: "loading" };
type Event = { type: "FETCH" } | { type: "CANCEL" };

function reduce(s: State, e: Event): State {
  if (s.status === "loading" && e.type === "CANCEL") return { status: "idle" };
  if (s.status === "idle" && e.type === "FETCH") return { status: "loading" };
  return s; // ignore invalid combos
}
console.log(reduce({ status: "loading" }, { type: "CANCEL" }).status);

網羅性を追加する

reducerとassertNeverを組み合わせると、新しい状態やイベントを追加したときに、更新が必要なすべての箇所が明らかになります。

function assertNever(x: never): never { throw new Error("unhandled"); }
type State = { status: "idle" } | { status: "loading" } | { status: "done"; data: string };

function view(s: State): string {
  switch (s.status) {
    case "idle": return "idle";
    case "loading": return "loading";
    case "done": return s.data;
    default: return assertNever(s);
  }
}
console.log(view({ status: "done", data: "ok" }));

初期状態と終了状態

明確な開始状態(多くの場合はidle)と終了状態(successまたはerror)を定めます。これにより、マシンのライフサイクルを明示できます。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

const initial: State = { status: "idle" };
console.log("Start:", initial.status);

完全なミニステートマシン

ここで処理の全体を確認しましょう。idleから開始し、fetchでloadingに移り、完了するとsuccessになります。型によって、すべてのステップが正しいことを保証できます。

type State = { status: "idle" } | { status: "loading" } | { status: "success"; data: string };
type Event = { type: "FETCH" } | { type: "RESOLVE"; data: string };

function reduce(s: State, e: Event): State {
  if (e.type === "FETCH") return { status: "loading" };
  return { status: "success", data: e.data };
}
let st: State = { status: "idle" };
st = reduce(st, { type: "FETCH" });
st = reduce(st, { type: "RESOLVE", data: "hi" });
console.log(st.status, st.status === "success" ? st.data : "");

クイックチェック:ステートマシン

ステートマシンのモデル化についての理解度を確認しましょう。

まとめ:ステートマシンのモデル化

idle/loading/success/errorを判別可能なユニオンとしてモデル化し、状態とイベントのユニオンに対する型安全な遷移を記述し、マシンが成長しても正しく保てるように網羅性を利用しました。

type State = { status: "idle" } | { status: "loading" } | { status: "success"; data: string };
const s: State = { status: "success", data: "done" };
console.log(s.status === "success" ? s.data : "");

よくある質問

「ステートマシンのモデル化」レッスンは無料ですか?

はい。「ステートマシンのモデル化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全4レッスンが含まれています。

「ステートマシンのモデル化」で何を学びますか?

判別可能なユニオンで有限個の状態を表現します。 ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

TypeScript Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「ステートマシンのモデル化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このTypeScript Academyレッスンでコードを書いて実行できますか?

はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 判別可能なユニオンの構築
  2. 判別プロパティによる型の絞り込み
  3. neverによる網羅性チェック
  4. ステートマシンのモデル化
← TypeScript Academyに戻る