# 类型收窄
function setWidth(width: number | string) {
if (typeof width === 'number') {
return width + 'px';
}
return width;
}
这里的 typeof
就把width
的类型收窄为 number
或者 string
中的其中一个, 然后就可以做对应的操作了。
TypeScript 已经实现了比如 if/else
、三元运算符、循环、真值检查等情况下的类型分析。TypeScript 会认为 typeof padding === number
是一种特殊形式的代码,我们称之为类型保护 (type guard),TypeScript 会沿着执行时可能的路径,分析值在给定的位置上最具体的类型。TypeScript 的类型检查器会考虑到这些类型保护和赋值语句,而这个将类型推导为更精确类型的过程,我们称之为收窄 (narrowing)
# typeof 类型保护(type guards)
JavaScript 本身就提供了 typeof
操作符,可以返回运行时一个值的基本类型信息,会返回如下这些特定的字符串:
- "string"
- "number"
- "bigInt"
- "boolean"
- "symbol"
- "undefined"
- "object"
- "function"
# 真值收窄(Truthiness narrowing)
其实也就是通过if判断值的真假, 来实现类型的具体化,举个例子
function printStr(strs: string | string[] | null) {
if (strs && Array.isArray(strs)) {
for (const s of strs) {
console.log(s);
}
} else {
console.log(strs);
}
}
# 等值收窄(Equality narrowing)
function foo(x: number | string, y: string | boolean) {
if (x === y) {
console.log(x.toLowerCase());
} else {
console.log(x, y);
}
}
比如上面的例子, 判断x和y完全相等, 但是他们的唯一共同类型只有string, 所以类型会被收窄为string。
当然判断更具体的值也会让类型收窄, 比如具体判断是不是等于null
function printStr(strs: string | string[] | null) {
if (strs !== null) {
if (Array.isArray(strs)) {
for (const s of strs) {
console.log(s);
}
} else {
console.log(strs);
}
}
}
# in 操作符
const obj = { x: 1 };
if ('x' in obj) {
console.log(obj.x);
}
也能做到类型收窄
# instanceOf
instanceof
也是一种类型保护,TypeScript 也可以通过识别 instanceof
正确的类型收窄:
# 赋值语句(Assignments)
其实就是自动类型推论, 是一个道理
// x 会首先被推断为 'hello' | 10
let x = Math.random() > 0.5 ? 'hello' : 10;
x = 1 // 当然重新赋值一个数字也是可以的
console.log(x.toFixed(2));
x = true // 重新赋值一个boolean是不允许的
# 类型判断式
class Fish {
swim() {
console.log('swim');
}
}
class Bird {
fly() {
console.log('fly');
}
}
function getPet(): Fish | Bird {
return Math.random() > 0.5 ? new Fish() : new Bird();
}
const pet = getPet();
// 报错: 类型“Fish | Bird”上不存在属性“swim”。
/* if(pet.swim) {
pet.swim()
} */
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
其实我觉得有点麻烦 不如断言一下来的更快
# 穷尽检查
never 类型可以赋值给任何类型,然而,没有类型可以赋值给 never
(除了 never
自身)。这就意味着你可以在 switch
语句中使用 never
来做一个穷尽检查。
enum ShapeKind {
CIRCLE = 'circle',
SQUARE = 'square'
}
interface Circle {
kind: ShapeKind.CIRCLE;
radius: number;
}
interface Square {
kind: ShapeKind.SQUARE;
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case ShapeKind.CIRCLE:
return Math.PI * shape.radius * shape.radius;
case ShapeKind.SQUARE:
return shape.sideLength * shape.sideLength;
default:
const _exhaustiveShape: never = shape;
return _exhaustiveShape;
}
}
这里我们采用switch穷尽检查kind属性, 这时候如果新增一个类型
enum ShapeKind {
CIRCLE = 'circle',
SQUARE = 'square',
TRIANGLE = 'Triangle'
}
interface Triangle {
kind: ShapeKind.TRIANGLE;
bottomWidth: number;
height: number;
}
type Shape = Circle | Square | Triangle;
因为 TypeScript 的收窄特性,执行到 default
的时候,类型被收窄为 Triangle
,但因为任何类型都不能赋值给 never
类型,这就会产生一个编译错误。通过这种方式,你就可以确保 getArea
函数总是穷尽了所有 shape
的可能性。
← TypeScript-高级类型 模块 →