类型缩小
TypeScript 遵循我们的程序可以采用的可能执行路径来分析给定位置的值的最具体的可能类型。它着眼于这些特殊检查(称为类型保护)和赋值,将类型精炼为比声明的更具体的类型的过程
称为缩小。
function padLeft(padding: number | string, input: string): string {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
//(parameter) padding: number
}
return padding + input;
//(parameter) padding: string
}
TypeScript 可以理解几种不同的结构来缩小类型。
typeof 类型保护
支持 typeof 运算符: TypeScript 可以理解它来缩小不同分支中的类型。
- “string”
- “number”
- “bigint”
- “boolean”
- “symbol”
- “undefined”
- “object”
- “function”
注意在上面的列表中,typeof 不返回字符串 null
真值缩小
我们可以在条件、&&、||、if 语句、布尔否定 (!) 等中使用任何表达式
function getUsersOnlineMessage(numUsersOnline: number) {
if (numUsersOnline) {
return `There are ${numUsersOnline} online now!`;
}
return "Nobody's here. :(";
}
像 if 这样的构造首先将它们的条件 “强制转换” 到 boolean 来理解它们,然后根据结果是 true 还是 false 来选择它们的分支
- 0
- NaN
- “” (空字符串)
- 0n(bigint版本零)
- null
- undefined
以上全部强制转换为 false,其他值强制转换为 true
相等性缩小
使用 switch 语句和 =、!、== 和 != 等相等性检查来缩小类型
function example(x: string | number, y: string | boolean) {
if (x === y) {
// We can now call any 'string' method on 'x' or 'y'.
x.toUpperCase();
//(method) String.toUpperCase(): string
y.toLowerCase();
//(method) String.toLowerCase(): string
} else {
console.log(x);
//(parameter) x: string | number
console.log(y);
//(parameter) y: string | boolean
}
}
in 运算符缩小
in 运算符:确定对象或其原型链是否具有名称属性
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
if ("swim" in animal) {
animal;
(parameter) animal: Fish | Human
} else {
animal;
(parameter) animal: Bird | Human
}
}
instanceof 缩小
instanceof:用于检查一个值是否是另一个值的 “instance”
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
//(parameter) x: Date
} else {
console.log(x.toUpperCase());
//(parameter) x: string
}
}
赋值
当我们为任何变量赋值时,TypeScript 会查看赋值的右侧并适当地缩小左侧
let x = Math.random() < 0.5 ? 10 : "hello world!";
let x: string | number
x = 1;
console.log(x);
//let x: number
x = "goodbye!";
console.log(x);
//let x: string
x = true;
// Type 'boolean' is not assignable to type 'string | number'.
控制流分析
控制流分析
:基于可达性的代码分析称为控制流分析
控制流可以一次又一次地分裂和重新合并,并且可以观察到该变量在每个点具有不同的类型
function example() {
let x: string | number | boolean;
x = Math.random() < 0.5;
console.log(x);
let x: boolean
if (Math.random() < 0.5) {
x = "hello";
console.log(x);
let x: string
} else {
x = 100;
console.log(x);
let x: number
}
return x;
let x: string | number
}
使用类型谓词
希望更直接地控制类型在整个代码中的变化方式
要定义用户定义的类型保护,我们只需要定义一个返回类型为类型谓词的函数
谓词采用 parameterName is Type
的形式,其中 parameterName 必须是当前函数签名中的参数名称
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
pet is Fish
是本例中的类型谓词
任何时候使用某个变量调用 isFish 时,如果基础类型兼容,TypeScript 就会将该变量缩小到该特定类型
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return isFish(pet);
});
断言函数
也可以使用 断言函数 缩小类型。
判别联合
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;
//(parameter) shape: Circle
}
}
同样的检查也适用于 switch 语句
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
//(parameter) shape: Circle
case "square":
return shape.sideLength ** 2;
//(parameter) shape: Square
}
}
never 类型
never 类型来表示不应该存在的状态。
缩小类型时,你可以将联合的选项减少到你已消除所有可能性并且一无所有的程度。在这些情况下,TypeScript 将使用 never 类型来表示不应该存在的状态。
穷举检查
never 类型可分配给每个类型;但是,没有类型可分配给 never(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;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
向 Shape 联合添加新成员,将导致 TypeScript 错误:
interface Triangle {
kind: "triangle";
sideLength: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
//Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}