类型细化
有的时候我们会对某个值做类型判断,而 TS 能从中推断该值更具体的类型:
function padLeft(padding: number | string, input: string) {
    if (typeof padding === 'number') {
        // 这里 TS 会推断出 `padding` 是 `number` 类型
        return ' '.repeat(padding) + input;
    }
    // 这里 TS 会推断出 `padding` 是 `string` 类型
    return padding + input;
}
这种类型推断的特性称为“类型细化”( Narrowing ),一般存在于各种控制语句上,比如 if-else、三元条件语句、循环、真值检查等。有许多种情况可以应用这一特性。
typeof 类型保护
typeof 是 JS 里的操作符,能够在运行时里提供值类型信息。TS 能够分析的 typeof 的返回值有:
- "string"
 - "number"
 - "bigint"
 - "boolean"
 - "undefined"
 - "object"
 - "function"
 
TS 有着自己的 typeof 处理,因为 JavaScript 中可能会存在一些奇怪的行为,比如 null 会被认为是 object 类型(实际上 null 自身是一种类型):
function printAll(strs: string | string[] | null) {
    if (typeof strs === 'object') {
        // ERROR: 这里的 `strs` 也有可能是 `null`
        for (const s of strs) {
            console.log(s);
        }
    } else if (typeof strs === 'string') {
        // ...
    }
}
像 typeof 这样类型检查的语句在 TS 中称为类型保护( Type guard )。
真值检查
在 JS 里我们可以在条件语句、&&、||、if 语句或否定符 ! 等里使用任何表达式。比如我们可以在 if 里放非 boolean 类型的条件语句。JS 会将条件语句强制转为布尔值,比如这些值会被转为 false :
0
NaN
"" ,即空字符串
0n ,即
bigint中的 0nullundefined
你也可以用 Boolean 函数将值转为布尔值,或者用双否定 !! 。而后者其实会推断为一个布尔值字面量类型(比如例子中为 true ),前者仅仅是个 boolean 类型:
Boolean('hello'); // 类型为 `boolean` ,值为 `true`
!!'world'; // 类型和值均为 `true`
我们可以利用这一行为来进行空值检查:
function printAll(strs: string | string[] | null) {
    if (strs && typeof strs === 'object') {
        for (const s of strs) {
            console.log(s);
        }
    } else if (typeof strs === 'string') {
        // ...
    }
}
但是 不要 写成如下的例子,因为空字符串是 string 类型,但是会转为布尔值 false :
function printAll(strs: string | string[] | null) {
    if (strs) {
        if (typeof strs === 'object') {
            for (const s of strs) {
                console.log(s);
            }
        } else if (typeof strs === 'string') {
            // ...
        }
    }
}
判等语句
TS 会对 switch 语句,以及 ===、!==、==、!= 等判等操作中来细化类型:
function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // 这里 `x` 和 `y` 类型均被细化为 `string`
        x.toUpperCase();
        y.toLowerCase();
    } else {
        // 这里 `x` 类型被细化为 `string | number`
        console.log(x);
    }
}
对于 == 和 != 这类弱化的判等,TS 也会有正确的类型细化:
function anotherExample(x: number | null | undefined) {
    if (x != null) {
        // 这里 `x` 类型被细化为 `number`
        console.log(x);
    }
}
in 操作符
TS 会根据 in 操作语句来细化类型:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
    if ('swim' in animal) {
        return animal.swim();
    }
    return animal.fly();
}
instanceof 操作符
JS 中 instanceof 用于判断是否在某个类的原型链上。TS 也会据此细化类型:
function logValue(x: Date | string) {
    if (x instanceof Date) {
        // 这里 `x` 类型被细化为 `Date`
        console.log(x.toUTCString());
    } else {
        console.log(x.toUpperCase());
    }
}
赋值语句
在赋值的时候,TS 也会细化类型,不需要我们自己判断:
// 只有声明语句能确定变量类型
let x = Math.random() < 0.5 ? 10 : 'hello world!';
x = 1; // `x` 类型被细化为 `number`
x = 'goodbye!'; // `x` 类型被细化为 `string`
x = false; // ERROR, 不能将 `boolean` 赋值给 `string | number` 类型
类型细化不是类型改变。
类型断言
函数的返回值可以是个类型断言,这将告诉 TS 是否需要类型细化:
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}
let pet = getSmallPet();
// TS 会根据类型断言对 pet 进行类型细化
if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
}
如果没有 pet is Fish 的类型断言,那么条件语句里的 pet 将不会做类型细化。
可区分的联合类型
在下面这个例子中,Shape 是一个联合类型:
interface Circle {
    kind: 'circle';
    radius: number;
}
interface Square {
    kind: 'square';
    sideLength: number;
}
type Shape = Circle | Square;
但在处理 Shape 对象时,TS 可以根据 kind 属性来细化类型:
function getArea(shape: Shape) {
    switch (shape.kind) {
        case 'circle':
            // 这里 `shape` 类型被细化为了 `Circle` ,下同
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.sideLength ** 2;
    }
}
never 类型
在细化过程中,如果遇到了不可能存在的情况,TS 会用 never 类型代替:
function print(x: number | string) {
    if (typeof x === 'number') {
        // ...
    } else if (typeof x === 'string') {
        // ...
    } else {
        // 这是一个不可能达到的代码块,所以 TS 会将 x 类型标为 `never`
    }
}