作为一个例子,考虑 Square
和 Rectangle
的联合类型 Shape
。Square
和 Rectangle
有共同成员 kind
,因此 kind
存在于 Shape
中。
interface Square { kind: 'square'; size: number; } interface Rectangle { kind: 'rectangle'; width: number; height: number; } type Shape = Square | Rectangle;
如果你使用类型保护风格的检查(==
、===
、!=
、!==
)或者使用具有判断性的属性(在这里是 kind
),TypeScript 将会认为你会使用的对象类型一定是拥有特殊字面量的,并且它会为你自动把类型范围变小:
function area(s: Shape) { if (s.kind === 'square') { // 现在 TypeScript 知道 s 的类型是 Square // 所以你现在能安全使用它 return s.size * s.size; } else { // 不是一个 square ?因此 TypeScript 将会推算出 s 一定是 Rectangle return s.width * s.height; } }
详细的检查
通常,联合类型的成员有一些自己的行为(代码):
interface Square { kind: 'square'; size: number; } interface Rectangle { kind: 'rectangle'; width: number; height: number; } // 有人仅仅是添加了 `Circle` 类型 // 我们可能希望 TypeScript 能在任何被需要的地方抛出错误 interface Circle { kind: 'circle'; radius: number; } type Shape = Square | Rectangle | Circle;
一个可能会让你的代码变差的例子:
function area(s: Shape) { if (s.kind === 'square') { return s.size * s.size; } else if (s.kind === 'rectangle') { return s.width * s.height; } // 如果你能让 TypeScript 给你一个错误,这是不是很棒? }
你可以通过一个简单的向下思想,来确保块中的类型被推断为与 never
类型兼容的类型。例如,你可以添加一个更详细的检查来捕获错误:
function area(s: Shape) { if (s.kind === 'square') { return s.size * s.size; } else if (s.kind === 'rectangle') { return s.width * s.height; } else { // Error: 'Circle' 不能被赋值给 'never' const _exhaustiveCheck: never = s; } }
它将强制你添加一种新的条件:
function area(s: Shape) { if (s.kind === 'square') { return s.size * s.size; } else if (s.kind === 'rectangle') { return s.width * s.height; } else if (s.kind === 'circle') { return Math.PI * s.radius ** 2; } else { // ok const _exhaustiveCheck: never = s; } }
Switch
你可以通过 switch
来实现以上例子。
function area(s: Shape) { switch (s.kind) { case 'square': return s.size * s.size; case 'rectangle': return s.width * s.height; case 'circle': return Math.PI * s.radius ** 2; default: const _exhaustiveCheck: never = s; } }