美文网首页TypeScript基础你不知道的JavaScript
探索 TypeScript 类型注解 - 类型检查

探索 TypeScript 类型注解 - 类型检查

作者: zhilidali | 来源:发表于2020-01-01 16:54 被阅读0次

    Exploring TypeScript Type Annotations - Type Checking

    本文由 WowBar 团队首发于 GitHub

    作者: zhilidali

    欢迎来到 《探索 TypeScript 类型注解》 系列教程。

    上一篇介绍了如何创建自定义类型,
    本篇深入探索 TypeScript 编译器的静态类型检查机制。

    目录

    正文

    当数据类型操作不合理时,编译器静态编译时会提示错误。

    let obj = { a: 1 };
    // A. 编译器推断出 obj 的类型: let obj: { a: number; }
    
    // B. 编译器检查数据的使用是否合理,不合理时会抛出 Error
    obj.b; // Error: 属性 b 不存在
    obj.a = '2'; // Error:'2' 不能赋值给 number 类型
    

    通过上面编译器对类型注解的静态检查,可以初步了解到类型检查的机制:编译器会适当的推断数据的类型,以及检查类型的使用是否合理,下面我们对类型检查机制进一步探索。

    类型推断

    Type Inference 类型推断:当没有显式指定类型注解时,编译器会推断出一个类型。

    • Basic Type Inference 基本类型推断
    • Best Common Type 最佳通用类型
    • Contextual Typing 上下文类型

    基本类型推断

    在定义变量、设置函数参数默认值、函数返回值时,编译器都会自动进行类型推断:

    // 在变量初始化时推断
    let foo;
    // let foo: any
    let bar = 'ts';
    // let bar: string
    
    // 推断函数参数和返回值类型
    let a = (x = 1) => {}
    // let a: (x?: number) => void
    

    类型推断在一定程度上可以保持代码简洁易读,但有时并不总是如此

    let foo;
    let f = foo;
    // let f: undefined
    
    let b: string | number = 'TS';
    let baz = b;
    // let baz: string
    

    最佳通用类型

    从多个表达式的类型推断出最佳通用类型

    let foo = ['ts', 1];
    // let foo: (string | number)[]
    // string | number 为联合类型,描述数据可以是其中的一种类型
    

    上下文类型

    由上下文推断出表达式的类型

    let add: (a: number, b: number) => number;
    
    add = function(x, y) {
        // 由 add 可推断出 x、y 以及返回值的类型
        return x + y;
    };
    // add = function(x: number, y: number): number {
    //  return x + y;
    // };
    

    类型断言

    Type Assertion 类型断言可以让你告诉编译器当前数据的类型,有两种语法:

    • 尖括号语法: <T>value
    • as 语法: value as T (jsx 中只能用 as 语法)
    interface Foo {
        foo: number;
    }
    let a = {};
    // let a: {}
    let b = {} as Foo;
    // let b: Foo; 类型断言告诉编译器 b 的类型为 Foo
    
    a.foo; // Error
    (a as Foo).foo; // Ok
    b.foo; // Ok
    

    类型兼容

    如果类型 Y 可以赋值给类型 X,即 X = Y,那么我们说,目标类型 X 兼容源类型 Y。

    interface TypeA {
        a: string;
    }
    interface TypeB {
        a: string;
    }
    
    let foo: TypeA = {a: 'ts'}
    // 基于结构类型,所以 foo 可赋值给 bar,即 TypeB 兼容 TypeA
    let bar: TypeB = foo;
    

    TS 基于结构子类型设计 (因为 JS 中广泛的使用匿名对象,例如函数表达式和对象字面量),与名义类型形成对比。
    如上因为 TypeA 与 TypeB 结构相同 (属性及其类型),所以在结构类型系统中,它们是兼容的。
    而在名义类型系统中,类型的兼容性或等价性是基于声明和名称来决定的。

    基本类型兼容

    之前“数据类型”篇幅中已有介绍,详情请点击此处

    // `string` 兼容 `null`; `null` 是 `string` 的子类型
    let str: string = null;
    

    成员结构兼容

    必选成员少的兼容成员多的,即源类型至少具有与目标类型相同的成员

    基本结构兼容

    let foo: { a: string } = { a: 'a' };
    let bar: { a: string, b: string } = { a: 'a', b: 'b' };
    
    foo = bar; // OK, 少兼容多
    bar = foo; // Error
    
    // 函数返回值
    let foo = () => ({ a: 'a' });
    let bar = () => ({ a: 'a', b: 'b' });
    
    foo = bar; // Ok, 少兼容多
    bar = foo; // Error
    

    类兼容

    类的实例成员少的兼容成员多的 (比较两个类的对象时,静态成员不比较)

    class A {
        foo: number = 1
    }
    class B {
        foo: number = 2
        bar: number = 3
    }
    class C extends A {
        baz: number = 4;
    }
    
    let a = new A();
    let b = new B();
    let c = new C();
    
    a = b; // Ok, 少兼容多
    b = a; // Error
    
    c = a; // Error
    a = c; // Ok, 少兼容多
    

    枚举兼容

    枚举类型与数值类型相互兼容,枚举之间不兼容

    enum E { A, B }
    enum F { X, Y }
    
    // 枚举类型与数值类型相互兼容
    let foo: E = 123; // OK, 枚举类型兼容数值类型
    let bar: number = E.A; // OK, 数值类型兼容枚举类型
    // 枚举之间不兼容
    let baz = E.A;
    baz = F.X; // Error
    

    泛型兼容

    // 类型成员都为空,相互兼容
    interface Empty<T> {}
    let x: Empty<number> = {};
    let y: Empty<string> = {};
    
    x = y; // OK
    y = x; // OK
    
    // 成员类型不同,所以相互不兼容
    interface NotEmpty<T> {
        data: T;
    }
    let x: NotEmpty<number> = { data: 1 };
    let y: NotEmpty<string> = { data: '1' };
    
    x = y; // Error
    y = x; // Error
    

    函数参数个数兼容

    多兼容少

    • 参数多的兼容参数少的 (主要是因为 JS 中经常可以忽略多余的参数,比如 Array#map),
    • 当 strictFunctionTypes 为 true 时,参数少的不能兼容参数多的

    必选参数

    let foo = (a: number) => a;
    let bar = (a: number, b: number) => a + b;
    
    foo = bar; // Error, 当 strictFunctionTypes 为 true 时
    bar = foo; // OK, 多兼容少
    

    必选参数、可选参数、剩余参数

    let a = (p1: number, p2: number) => { }
    let b = (p1?: number, p2?: number) => { }
    let c = (...args: number[]) => {}
    
    a = b; // Ok
    a = c; // Ok
    
    b = a; // Error, 当 strictFunctionTypes 为 true 时
    b = c; // Error, 当 strictFunctionTypes 为 true 时
    
    c = a; // Ok
    c = b; // Ok
    

    命名参数

    let foo = (p: { a: number }) => p.a;
    let bar = (p: { a: number; b: number }) => p.a;
    
    foo = bar; // Error when strictFunctionTypes is true
    bar = foo; // OK
    

    类型保护

    许多表达式可以确保在某个作用域中运行时,数据有着更精确的类型,称为类型保护。

    常识别为类型保护的操作符

    • 相等运算符: === !==
    • 逻辑运算符: ||
    • 类型断言运算符: ! 去除 null 和 undefined
    function getLength(arg: string | null): number {
        arg.length; // Error,编译器检查到 arg 的类型为 string 或 null,所以不能访问 length 属性
        if (arg === null) {
            return 0; // 编译器可以推断出,在这个 if 子句中,arg 的类型是 null
        }
        return arg.length; // 编译器可以推断出,此处 arg 的类型是 string
    }
    

    类型谓词

    函数的返回值类型为类型谓词 parameterName is Type, 其中 parameterName 为函数参数。

    // arg is string 是类型谓词
    function isString(arg: any): arg is string {
        return typeof arg === 'string';
    }
    function f(foo: string | string[]) {
        if (isString(foo)) {
            foo.toUpperCase(); // OK
        } else if (Array.isArray(foo)) {
            foo.map(it => it.toUpperCase()); // OK
        }
    }
    

    可识别为类型保护的关键字

    如下三个 JS 关键字可以帮助 TS 进一步识别类型

    • typeof: type v === typename 其中 typename 为 boolean, number, string, symbol 时,TS 才会识别为类型保护。
    • instanceof
    • in
    function f(foo: string | string[]) {
        foo.toUpperCase(); // Error
        foo.map(it => it.toUpperCase());
        foo.toFixed(); // Error
    
        if (typeof foo === 'string') {
            foo.toUpperCase(); // OK
        } else if (foo instanceof Array) {
            foo.map(it => it.toUpperCase()); // OK
        }
    }
    function b(bar: { a: string } | { b: string }) {
        bar.a; // Error
        bar.b; // Error
    
        if ('a' in bar) {
            bar.a; // Ok
        } else {
            bar.b; // Ok
        }
    }
    

    结语

    本篇通过类型检查机制探索了类型检查的规则,下篇通过将探索 TypeScript 的高级类型。

    协议

    本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

    《探索 TypeScript 类型注解》

    相关文章

      网友评论

        本文标题:探索 TypeScript 类型注解 - 类型检查

        本文链接:https://www.haomeiwen.com/subject/tlhgoctx.html