二、类型收窄(Narrowing)

1.认识类型收窄

function handleType(val: string | number){
	if(typeof val === 'string'){
		return val.length; // (parameter) val: string
	}
	return val; // (parameter) val: number
}

这个代码看上去也许没有什么有意思的地方,但实际上,TypeScript 在背后做了很多东西。

TypeScript 要学着分析这些使用了静态类型的值在运行时的具体类型。目前 TypeScript 已经实现了比如 if/else 、三元运算符、循环、真值检查等情况下的类型分析。

在我们的 if 语句中,TypeScript 会认为 typeof val === 'string' 是一种特殊形式的代码,我们称之为类型保护 (type guard),TypeScript 会沿着执行时可能的路径,分析值在给定的位置上最具体的类型。

2.类型保护(type guards)

JavaScript 本身就提供了 typeof 操作符,可以返回运行时一个值的基本类型信息,会返回如下这些特定的字符串:

  • “string”
  • “number”
  • “bigInt”
  • “boolean”
  • “symbol”
  • “undefined”
  • “object”
  • “function”

typeof操作符在很多 JavaScript 库中都有着广泛的应用,而 TypeScript 已经可以做到理解并在不同的分支中将类型收窄。在 TypeScript 中,检查typeof返回的值就是一种类型保护。

function getText(str: number | string): string {
    if (typeof str === "number") {
        return `${str} isNumber`; // (parameter) str: number
    } else {
        return `${str} isString`; // (parameter) str: string
    }
}

3.等值收窄(Equality narrowing)

Typescript 也会使用 switch 语句和等值检查比如 =、!、==、!=,去收窄类型。比如:

function getText(a: string | boolean, b: string | null): void {
    if (a === b) {
        console.log(a);
        console.log(b);
        // (parameter) a: string
        // (parameter) b: string
    } else {
        console.log(a);
        console.log(b);
        // (parameter) a: string | boolean
        // (parameter) b: string | null
    }
}

ts的分析可能是这样的:

  1. 判断ab是否完全相等
  2. 完全相等,string类型是ab唯一可能的相同类型。所以ab一定是 string 类型。判断具体的字面量值也能让 TypeScript 正确的判断类型。
  3. 不等,那就各自安好,不再纠结了~

4.四、in 操作符收窄

JavaScript 中有一个 in 操作符可以判断一个对象是否有对应的属性名。TypeScript 也可以通过这个收窄类型。

type Dog = { ww: "1"; mm?: "2" };
type Cat = { mm: "1" };

function inDemo(animal: Dog | Cat): void {
    if ("ww" in animal) {
        console.log(animal); // (parameter) animal: Dog
    } else {
        console.log(animal); // (parameter) animal: Cat
    }
}

通过 “ww” in animal中,我们可以推断出,animal一定是Dog类型,类型收窄。

而如果有可选属性,Ts也会检测出来:

type Dog = { ww: "1"; mm?: "2" };
type Cat = { mm: "1" };

function inDemo(animal: Dog | Cat): void {
    if ("mm" in animal) {
        console.log(animal); // (parameter) animal: Dog | Cat
    } else {
        console.log(animal); // (parameter) animal: Dog
    }
}

mm 在 animal中是不能正确收窄的,但如果不在 animal 中,则可以推断出其是 Dog 类型。

5.instanceof 收窄

instanceof 也是一种类型保护,TypeScript 也可以通过识别 instanceof 正确的类型收窄:

function instanceofDemo(a: object | number): void {
    if (a instanceof String) {
        console.log(a);
        // (parameter) a: String
    } else {
        console.log(a);
        // (parameter) a: number | object
    }
}

6.赋值语句(Assignments)

TypeScript 可以根据赋值语句的右值,正确的收窄左值。

let x = Math.random() > 0.5 ? "abc" : 123; // x: string | number

x = 1; // x: number
x = "1"; // let x: string

注意这些赋值语句都有有效的,即便我们已经将 x 改为 number 类型,但我们依然可以将其更改为 string 类型,这是因为 x 最初的声明为 string | number,赋值的时候只会根据正式的声明进行核对。

写在最后

本篇文章是《Typescript基础入门》第二篇,收窄是一组比较难理解的思路,这里仅提到部分常见的形式,一起共勉吧!

参考: