TS文档 --- 类型收窄和类型保护
本篇整理自 TypeScript Handbook 中 「Narrowing」 章节。
Narrowing
// 将类型推导为更精确类型的过程,我们称之为收窄 (narrowing)
function padLeft(padding: number | string, input: string) {
// typeof padding === number --- 类型保护 (type guard)
if (typeof padding === "number") {
return new Array(padding + 1).join(" ") + input;
}
return padding + input;
}
在编辑器中,我们可以观察到类型的改变:
从上图中可以看到在 if
语句中,和剩余的 return
语句中,padding
的类型都推导为更精确的类型。
接下来,我们就介绍 narrowing
所涉及的各种内容。
typeof 类型保护(type guards)
在 TypeScript 中,检查 typeof
返回的值就是一种类型保护。TypeScript 知道 typeof
不同值的结果,它也能识别 JavaScript 中一些怪异的地方
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return new Array(padding + 1).join(" ") + input;
}
return padding + input;
}
真值收窄(Truthiness narrowing)
if
语句不需要条件的结果总是 boolean
类型,这是因为 JavaScript 会做隐式类型转换,
像 0
、NaN
、""
、0n
、null
undefined
这些值都会被转为 false
,其他的值则会被转为 true
。
也可以使用 Boolean
函数强制转为 boolean
值,或者使用更加简短的!!
function multiplyAll(
values: number[] | undefined,
factor: number
): number[] | undefined {
if (!values) {
return values;
// (parameter) values: undefined
} else {
return values.map((x) => x * factor);
// (parameter) values: number[]
}
}
等值收窄(Equality narrowing)
Typescript 也会使用 switch
语句和等值检查比如 ==
!==
==
!=
去收窄类型
判断具体的字面量值也能让 TypeScript 正确的判断类型。
此时上述的例子中,就可以正常处理strs
的值为空字符串的情况
JavaScript 的宽松相等操作符如 ==
和 !=
也可以正确的收窄
不过在 JavaScript 中,通过 == null
这种方式并不能准确的判断出这个值就是 null
,它也有可能是 undefined
。对 == undefined
也是一样
in 操作符收窄
JavaScript 中有一个 in
操作符可以判断一个对象是否有对应的属性名。TypeScript 也可以通过这个收窄类型。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
// (parameter) animal: Fish
}
return animal.fly();
// (parameter) animal: Bird
}
instanceof 收窄
instanceof
也是一种类型保护,TypeScript 也可以通过识别 instanceof
正确的类型收窄
赋值语句(Assignments)
TypeScript 可以根据赋值语句的右值,正确的收窄左值。
注意这些赋值语句都有有效的,即便我们已经将 x
改为 number
类型,但我们依然可以将其更改为 string
类型,这是因为 x
最初的声明为 string | number
,赋值的时候只会根据正式的声明进行核对。
控制流分析(Control flow analysis)
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return new Array(padding + 1).join(" ") + input;
}
return padding + input;
}
在第一个 if
语句里,因为有 return
语句,TypeScript 就能通过代码分析,判断出在剩余的部分 return padding + input
,如果 padding 是 number
类型,是无法达到 (unreachable) 这里的,所以在剩余的部分,就会将 number
类型从 number | string
类型中删除掉。
这种基于可达性(reachability) 的代码分析就叫做控制流分析(control flow analysis)。
类型谓词(type predicates)
所谓 predicate
就是一个返回 boolean
值的函数。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
// pet is Fish 就是类型谓词
// 格式为parameterName is Type
// 其中parameterName 必须是当前函数的参数名
function isFish(pet: Fish | Bird): pet is Fish {
return 'swim' in pet;
}
可辨别联合(Discriminated unions)
当联合类型中的每个类型,都包含了一个共同的字面量类型的属性,TypeScript 就会认为这是一个可辨别联合(discriminated union),然后可以将具体成员的类型进行收窄。
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
在这个例子中,kind
就是这个公共的属性(作为 Shape 的可辨别(discriminant) 属性 )
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
} else {
return shape.sideLength ** 2
}
}
never 类型
当进行收窄的时候,如果你把所有可能的类型都穷尽了,TypeScript 会使用一个 never
类型来表示一个不可能存在的状态。
穷尽检查(Exhaustiveness checking)
除了 never
自身,没有类型可以赋值给 never
。这就意味着你可以在 switch
语句中使用 never
来做一个穷尽检查。
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
// 只有当Shape的所有类型都被处理完毕
// default分支中的shape的类型才会是never
// 当我们给 Shape 类型添加一个新成员,却没有做对应处理的时候,就会导致一个 TypeScript 错误
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
转载自:https://juejin.cn/post/7038627026009325605