你将用可辨识联合类型为你的状态建模,并用 never 类型锁定它们的穷尽性。
在 Kodokon 中打开本课never 是空类型:没有任何值属于它。当编译器证明某条代码路径不可能到达时,它就会出现——例如在穷尽了一个联合类型的每个成员之后。这一特性使它成为一种设计工具:如果你能让编译器在 switch 的默认分支中说出 never,你就得到了每个情况都被处理的静态证明。这正是穷尽性检查(exhaustiveness checking)的基础。
你在上一模块中已经初识可辨识联合类型;现在是时候把它变成一件真正的设计工具了。相比一个塞满可选字段的单一接口,可辨识联合类型让不可能的状态无法被表示:你不可能同时拥有 data 和 error,也不可能有一个没有数据的成功状态。它是加载状态、操作结果和事件消息的首选模式。
type FetchState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
function render(state: FetchState<string[]>): string {
switch (state.status) {
case "idle": return "Waiting";
case "loading": return "Loading…";
case "success": return state.data.join(", ");
case "error": return state.error.message;
}
}你已经见过在 default 分支中调用的 assertNever 守卫;现在让我们精确剖析它是如何工作的。只要每个情况都被覆盖,它的参数在此处就是 never 类型,一切都能编译通过。若你向联合类型添加一个变体却不更新 switch,那个被遗漏的变体就不再能赋值给 never:出现编译期错误,且错误信息中会带上缺失情况的名称。你由此把一个生产环境的 bug 变成了一次被拒绝的构建。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "rect"; width: number; height: number };
function assertNever(value: never): never {
throw new Error("Unhandled case: " + String(value));
}
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "rect": return shape.width * shape.height;
default: return assertNever(shape);
}
}有两个进阶细节值得你留意。其一,判别式的收窄只有在你直接测试外层对象时,才会传播到嵌套结构中;单独解构判别式会切断这种关联(除了自 TypeScript 4.6 起对联合解构所支持的情形)。其二,never 在联合类型中会被吸收(string | never 就是 string):这正是条件类型能借以过滤掉某些变体的原因,Exclude 就是这样做的。
type Event =
| { type: "click"; x: number; y: number }
| { type: "keypress"; key: string };
type EventOf<T extends Event["type"]> =
Extract<Event, { type: T }>;
type ClickEvent = EventOf<"click">;
function handle<T extends Event["type"]>(
type: T,
handler: (event: EventOf<T>) => void
): void {
/* register the handler */
}
handle("click", (e) => console.log(e.x, e.y));Shape 添加一个变体,却不修改包含 assertNever 的那个 switch,会发生什么?string | never 是什么类型?neverstringunknownstring | undefined