0Pricing
TypeScript Academy · 课时

使用 never 进行穷尽性检查

使用 never 在编译时捕获未处理的情况。

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

穷尽性问题

添加新的联合成员后,很容易忘记在某个位置处理它。穷尽性检查会将这种疏漏转化为编译错误。

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

// If we add "triangle" later, we want every switch to complain.

永不存在类型

never 类型表示永远不会出现的值。如果所有分支都已处理,到达默认分支的值类型就是 never。

function fail(): never {
  throw new Error("unreachable");
}
// never is assignable to nothing except never itself.

在默认分支中赋值给永不存在类型

在 default 分支中,将该值赋给一个 never 变量。如果所有变体都已处理,赋值就能通过编译;否则会产生错误。

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    default:
      const _exhaustive: never = s;
      return _exhaustive;
  }
}
console.log(area({ kind: "square", side: 3 }));

忘记某个分支时会发生什么

如果添加了 triangle 成员却忘记添加对应分支,那么默认分支中的 s 就不再是 never,因此赋值会在编译时失败。

// type Shape = ... | { kind: "triangle"; base: number; height: number };
// Now in default, s is { kind: "triangle"; ... }
// const _exhaustive: never = s; // Error: triangle not assignable to never

assertNever 辅助函数

可复用的 assertNever 函数可以集中实现这种模式。它接收 never 并抛出错误,同时说明该分支本应无法到达。

function assertNever(value: never): never {
  throw new Error("Unhandled case: " + JSON.stringify(value));
}
console.log(typeof assertNever);

在分支选择中使用 assertNever

在默认分支中调用 assertNever(s)。它会在编译时强制执行穷尽性检查,并在运行时意外到达该分支时提供清晰的错误。

function assertNever(value: never): never {
  throw new Error("Unhandled: " + JSON.stringify(value));
}
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    default: return assertNever(s);
  }
}
console.log(area({ kind: "circle", radius: 1 }).toFixed(2));

编译时安全与运行时安全

never 检查会在运行代码之前捕获遗漏的分支;如果某些情况绕过了类型检查,抛出的错误还可以在运行时提供保护。

function assertNever(x: never): never {
  throw new Error("Unhandled: " + String(x));
}
// Compile error if a case is missing; runtime throw as a backstop.
console.log("two layers of safety");

不使用默认分支进行穷尽性检查

如果函数具有显式返回类型,并且分支选择语句覆盖了每个分支,TypeScript 还可以标记缺少返回值的情况,这也是一种穷尽性检查。

type Light = "red" | "yellow" | "green";
function next(l: Light): Light {
  switch (l) {
    case "red": return "green";
    case "yellow": return "red";
    case "green": return "yellow";
  }
  // No default needed; all cases return.
}
console.log(next("red"));

使用条件分支链进行穷尽性检查

同样的思路也适用于 if/else。处理完每个变体后,最后的 else 会接收到一个 never 值。

function assertNever(x: never): never { throw new Error("bad"); }
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function name(s: Shape): string {
  if (s.kind === "circle") return "circle";
  else if (s.kind === "square") return "square";
  else return assertNever(s);
}
console.log(name({ kind: "square", side: 2 }));

为什么永不存在类型是正确工具

由于 never 不能赋值给任何其他类型,任何遗留的变体都会导致赋值失败。因此,never 是检测未处理分支的理想工具。

// Only never is assignable to never.
let x: never;
// x = "hi"; // Error
// Any concrete leftover type fails the same way.
console.log("never catches gaps");

将穷尽性检查作为重构安全网

在各处使用 assertNever 后,添加联合成员会产生一份整洁的编译错误列表,准确指出每个需要更新的位置。

function assertNever(x: never): never { throw new Error("unhandled"); }
type Status = "idle" | "busy";
function render(s: Status): string {
  switch (s) {
    case "idle": return "Idle";
    case "busy": return "Busy";
    default: return assertNever(s);
  }
}
console.log(render("idle"));

快速检查:穷尽性

测试您对穷尽性检查的理解。

回顾:使用永不存在类型进行穷尽性检查

您已经学会将默认值赋给 never(或将它传递给 assertNever),从而让编译器强制您处理每个变体。这样可以将遗漏的分支转化为编译错误。

function assertNever(x: never): never { throw new Error("unhandled"); }
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function f(s: Shape) {
  switch (s.kind) {
    case "circle": return s.radius;
    case "square": return s.side;
    default: return assertNever(s);
  }
}
console.log(f({ kind: "circle", radius: 5 }));

常见问题解答

「使用 never 进行穷尽性检查」课时是免费的吗?

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

「使用 never 进行穷尽性检查」这节课中我会学到什么?

使用 never 在编译时捕获未处理的情况。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

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

「使用 never 进行穷尽性检查」课时需要多长时间?

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

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

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

此课程中的所有课时

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