Typescript 辨别联合类型
kaichi • 2021-04-02
3 分钟 • 0 阅读
在使用联合类型时,通常需要判断联合类型的具体类型,考虑下面的例子。
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Square | Rectangle;
Square
和 Rectangle
有共同字面量成员 kind
,一般联合类型的成员有一些自己的行为,此时可以根据 kind
属性来判断联合类型,例如下面计算面积的 function。
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;
}
}
一切都很完美,如果此时 Shape
类型增加了一种类型。
interface Circle {
kind: 'circle';
radius: number;
}
type Shape = Square | Rectangle | Circle;
这时计算面积的 Function 缺少对 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 {
// 此时类型被判断为 `Circle`
// 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 {
// 此时类型收缩为 `never`
const _exhaustiveCheck: never = s;
}
}
你可以通过 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:
// 此时类型收缩为 `never`
const _exhaustiveCheck: never = s;
}
}
如果你使用 strictNullChecks
选项来做详细的检查,你应该返回 _exhaustiveCheck
变量(类型是 never
),否则 TypeScript 可能会推断返回值为 undefined
。
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;
return _exhaustiveCheck;
}
}