理解类型扩展与收缩
在TypeScript中,类型系统的一个重要特性是能够根据代码的上下文自动扩展或收缩变量的类型。这一机制使得TypeScript既能提供强大的类型检查,又能保持一定的灵活性。
类型扩展(Widening)
类型扩展是指TypeScript在某些情况下会将一个变量的类型从更具体的类型"扩展"为更宽泛的类型。这种情况通常发生在:
-
使用
let
或var
声明变量时:typescriptlet x = 3; // 类型被推断为number,而不是字面量类型3
-
初始化变量为
null
或undefined
时:typescriptlet y = null; // 类型被推断为any y = "hello"; // 合法,因为y的类型被扩展为any
-
在对象字面量中:
typescriptconst obj = { x: 1 }; // obj.x的类型被推断为number,而不是1
类型收缩(Narrowing)
类型收缩是类型扩展的逆过程,它通过特定的语法或结构将宽泛的类型缩小为更具体的类型。TypeScript提供了多种方式来实现类型收缩:
-
类型守卫(Type Guards):
typescriptfunction padLeft(padding: number | string, input: string) { if (typeof padding === "number") { return " ".repeat(padding) + input; // 这里padding的类型被收缩为number } return padding + input; // 这里padding的类型被收缩为string }
-
真值检查(Truthiness narrowing):
typescriptfunction printAll(strs: string | string[] | null) { if (strs && typeof strs === "object") { for (const s of strs) { // strs被收缩为string[] console.log(s); } } else if (typeof strs === "string") { console.log(strs); } }
-
相等性检查(Equality narrowing):
typescriptfunction example(x: string | number, y: string | boolean) { if (x === y) { // 这里x和y的类型都被收缩为string x.toUpperCase(); y.toLowerCase(); } }
-
in
操作符检查:typescripttype Fish = { swim: () => void }; type Bird = { fly: () => void }; function move(animal: Fish | Bird) { if ("swim" in animal) { return animal.swim(); // animal被收缩为Fish } return animal.fly(); // animal被收缩为Bird }
-
instanceof
检查:typescriptfunction logValue(x: Date | string) { if (x instanceof Date) { console.log(x.toUTCString()); // x被收缩为Date } else { console.log(x.toUpperCase()); // x被收缩为string } }
-
控制流分析(Control flow analysis):
typescriptfunction example() { let x: string | number | boolean; x = Math.random() < 0.5; console.log(x); // x是boolean if (Math.random() < 0.5) { x = "hello"; console.log(x); // x是string } else { x = 100; console.log(x); // x是number } return x; // x是string | number }
自定义类型守卫
当内置的类型收缩机制不够用时,可以创建自定义类型守卫函数:
typescript
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim(); // pet被收缩为Fish
} else {
pet.fly(); // pet被收缩为Bird
}
类型收缩的注意事项
-
never
类型:当类型收缩到不可能的情况时,TypeScript会使用never
类型表示:typescriptfunction getArea(shape: Circle | Square): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.sideLength ** 2; default: const _exhaustiveCheck: never = shape; // 确保所有情况都被处理 return _exhaustiveCheck; } }
-
可辨识联合(Discriminated unions):这是一种利用类型收缩的强大模式:
typescriptinterface Circle { kind: "circle"; radius: number; } interface Square { kind: "square"; sideLength: number; } 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; } }
理解类型扩展与收缩是掌握TypeScript类型系统的关键。通过合理利用这些特性,可以编写出既安全又灵活的TypeScript代码。