typescript之联合类型与类型保护

前言

联合类型与类型保护两者通常结合使用,因为在开发时typescript无法确切的知道一个联合类型具体是哪个类型,所以需要使用类型保护方式明确的指明。

联合类型

联合类型语法为: Type1 | Type2 | Type3

将指定的类型使用 | 管道符分割开,表示取值类型只能为这些类型。

    let unionType: string | number

    unionType = 21
    unionType = '字符串'
    unionType = true  // 报错!!! 布尔值不在指定的类型当中

可见设置unionType的变量类型为 string | number, 第5行因给变量赋值为布尔值所以报错了。

也能将联合类型作用于函数参数、函数返回值、数组使用:

    // 作用于函数
    function myFunc(param: string | number) : string | number {
        return 123 
        return '字符串'
        return true // 报错!!! 布尔值不在指定的类型当中
    }

    // 作用于数组
    let myArr: string[] | number[]
    myArr = ['字符串']
    myArr = [123]
    myArr = [true] // 报错!!! 元素只能全是string或者全是number类型。

 

当typescript不知道一个联合类型具体是哪种类型的时候,只能防问联合类型中所有类型的公共属性或方法。

什么意思呢? 看下面的例子:

这个变量是string跟number的联合类型,并没有给它赋值,所以准备调用它的方法时只能调用 string跟number都共有的方法。

 

为什么这里能防问到字符串的方法呢? 因为typescript有个类型推断的机制,当你给这个类型赋值为一个字符串时,聪明的typescript就已知道这个变量是字符串类型了。

联合类型的问题

问题来了〜 前文说到 :

当typescript不知道一个联合类型具体是哪种类型的时候,只能防问联合类型中所有类型的公共属性或方法

当我们将联合类型作为函数的返回参数时,外部并不能知道我们反回的到底是哪个类型,因为一个函数内部根据条件分支可能会有多个 return 语句, 如下例:

    function getValue(isNumber: boolean) : number | string {
        if(isNumber) {
            return 123
        } else {
            return '字符串'
        }
    }

    let value = getValue(true);
    value.toFixed() // !!! 报错

这是一个反回类型为number或string的联合类型函数,第9行调用函数传入 true,我们很清楚的知道会返回一个number类型的123, 但是typescript不知道,因为typescript并不知道函数体里写的是什么,所以第10行调用number类型特有的toFixed方法报错了。

如何让typescript知道或者说如何告诉typescript value变量的具体类型呢?

使用类型保护解决

类型保护是一个抽象的词,具体有多种方法来实现。

1.类型断言

    let value = getValue(true);
    (value as number).toFixed()
    (value as number).toExponential()

使用类型断言告诉typescript,value这个变量就是number类型的。

类型断言:有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

2.类型谓词

使用类型类型断言当只能针对一次防问语句,多次防问需要断言多次很麻烦,使用类型谓词可以在之后的某一作用域判断都有效:

    let value = getValue(true);

    function isNumber(value: string | number): value is number {
        return (value as number).toFixed !== undefined
    }

    if(isNumber(value)) {
        value.toFixed()
        value.toExponential()
    } else {
        value.length
        value.charAt(1)
    }

我们需要先定义一个函数 isNumber, 函数内进行断言判断,函数返回值是一个类型谓词如果函数内返回true表示这个value是number类型。

经过判断后第7行中的if作用域中能清楚的知道这是一个number类型的变量,而且第10行else作用域中也能明白这是一个string类型的变量,因为这个复合类型只有两种选择不是number就是string, 如果这个复合对象不止两种类型则不能这样做。

3.typeof 类型保护

使用类型谓词需要定义一个函数也挺麻烦的, typescript中使用typeof能用于类型保护:

    let value = getValue(true);

    if(typeof value === 'number') {
        value.toFixed()
        value.toExponential()
    } else {
        value.length
        value.charAt(1)
    }

使用typeof进行类型保护只能判断基本类型: numberstringbooleansymbol, 操作符只能是 ===!==,也可以不符合这些条件,但typescript不会识别成类型保护。

4.instance 类型保护

对于引用类型可以使用instance来判断,同样typescript也会将它识别成类型保护:

    function getValue(isNumber: boolean) : Number | String {
        if(isNumber) {
            return new Number(123)
        } else {
            return new String('字符串')
        }
    }

    let value = getValue(true);

    if((value instanceof Number)) {
        value.toFixed()
        value.toExponential()
    } else {
        value.length
        value.charAt(1)
    }

注意 getValue 函数中的number跟string都变成了以new的方式生成的引用类型。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注