0Pricing
TypeScript Academy · 课时

状态机建模

使用可辨识联合表示有限状态。

状态机建模 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。

将状态表示为可辨识联合

用户界面和异步流程具有不同的状态:空闲、加载中、成功和错误。将它们建模为可辨识联合,可以让每种状态只携带自身所需的数据。

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

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

每种状态的数据载荷

只有 success 状态具有 data,只有 error 状态具有 message。空闲和加载中状态不携带额外数据,这可以防止过期数据在状态之间泄漏。

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 进行分支选择的函数,可以为每种状态渲染正确的输出,并且只在字段存在的情况下访问成员字段。

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);

完整的状态转换函数

在一个归约函数中组合状态联合类型和事件联合类型。根据两者进行分支选择,可以让每个状态转换都明确且类型安全。

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);

添加穷尽性检查

将归约函数与 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);

完整的迷你状态机

完整流程如下:从空闲状态开始,获取数据后进入加载中状态,完成后进入成功状态。这些类型会确保每一步都符合预期。

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 : "");

快速检查:状态机

测试您对状态机建模的理解。

回顾:状态机建模

您已经将空闲、加载中、成功和错误建模为可辨识联合,围绕状态联合类型和事件联合类型编写了类型安全的状态转换,并使用穷尽性检查确保状态机在不断扩展时仍然正确。

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

常见问题解答

「状态机建模」课时是免费的吗?

是的 — 「状态机建模」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。

「状态机建模」这节课中我会学到什么?

使用可辨识联合表示有限状态。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「状态机建模」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 TypeScript Academy 课中编写并运行代码吗?

能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 构建可辨识联合
  2. 根据辨识属性进行收窄
  3. 使用 never 进行穷尽性检查
  4. 状态机建模
← 返回 TypeScript Academy