别再尝试 `typeof x === Fish`:TypeScript 类型验证实用指南(窄化 + 类型谓词)
发布: (2026年1月5日 GMT+8 22:45)
4 min read
原文: Dev.to
Source: Dev.to

当你来自“类型化思维”时,自然会想写类似下面的代码:
typeof animal === Fish
但 JavaScript 并不是这么工作的。
1) 核心思想:类型在运行时不存在
TypeScript 的类型在编译后会被擦除。运行时,你只能得到 JavaScript 的值。
运行时检查通常是下面这些形式:
typeof x === "string"x instanceof Date"swim" in animalanimal.kind === "fish"
TypeScript 会利用这些检查来 缩窄 联合类型。
2) 缩窄:TypeScript 能理解常见的 JS 模式
typeof 缩窄(原始类型)
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") return " ".repeat(padding) + input;
return padding + input;
}
instanceof 缩窄(类 / 构造函数)
function logValue(x: Date | string) {
if (x instanceof Date) return x.toUTCString();
return x.toUpperCase();
}
"in" 缩窄(属性是否存在)
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) animal.swim();
else animal.fly();
}
注意:
in会检查原型链,且可选属性会影响两个分支。
3) 类型谓词:让缩窄可复用(真正的优势)
当你需要在多个地方复用同一个检查(例如在 .filter() 中),类型谓词(用户自定义类型守卫)就派上用场:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(animal: Fish | Bird): animal is Fish {
return "swim" in animal;
}
function move(animal: Fish | Bird) {
if (isFish(animal)) animal.swim();
else animal.fly();
}
现在同一个函数可以被复用:
const zoo: (Fish | Bird)[] = [/* ... */];
const fishes = zoo.filter(isFish); // Fish[]
另一个带有辨别属性的例子:
type ButtonAsLink = { href: string; onClick?: never };
type ButtonAsAction = { onClick: () => void; href?: never };
type Props = { label: string } & (ButtonAsLink | ButtonAsAction);
function isLinkProps(p: Props): p is Props & ButtonAsLink {
return "href" in p;
}
function SmartButton(props: Props) {
if (isLinkProps(props)) {
return {props.label};
}
return {props.label};
}
4) 当你能控制模型时的最佳实践:辨别联合
如果可以修改数据结构,使用辨别联合是最稳妥的做法:
type Fish = { kind: "fish"; swim: () => void };
type Bird = { kind: "bird"; fly: () => void };
function move(animal: Fish | Bird) {
if (animal.kind === "fish") animal.swim();
else animal.fly();
}
这比属性检查更清晰,并且在联合类型扩展时也更易维护。
5) 常见陷阱(务必记住)
typeof null === "object"(历史遗留的 JS 奇怪行为)!value检查的是 假值(0、""、false),而不仅仅是null/undefined"prop" in obj可能因为原型链而为真- 可选属性可能导致两个分支仍然包含同一类型(例如
Human可能有swim?())
要点
- 运行时验证 来自 JavaScript 检查。
- 编译时安全 来自 TypeScript 的缩窄。
- 当需要复用时,把检查封装进 类型谓词。
“使用 JS 检查来缩窄类型,使用类型谓词来复用缩窄。”