• TS(6)-- 类型推论、类型兼容性、高级类型


    2019-11-09:

    学习内容:类型推论、类型兼容性、高级类型


    一、类型推论:类型是在哪里如何被推断的

      在有些没有明确指出类型的地方,类型推论会帮助提供类型。如:let x = 3;  变量x的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。大多数情况下,类型推论是直截了当地。

      最佳通用类型:如: let x = [0, 1, null];   两种选择:number 和 null , 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型。

    上下文类型:

      TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。

    window.onmousedown = function(mouseEvent) {
        console.log(mouseEvent.button);  //<- Error
    };

      这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出 mouseEvent参数的类型了。 如果函数表达式不是在上下文类型的位置, mouseEvent参数的类型需要指定为any,这样也不会报错了。


    二、类型的兼容性:

      TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。)

      TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。

      TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。

    interface Named {
        name: string;
    }
    
    let x: Named;
    // y's inferred type is { name: string; location: string; }
    let y = { name: 'Alice', location: 'Seattle' };
    x = y;

      这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是namestring类型成员。y满足条件,因此赋值正确。

      (2)比较两个函数:

      如果数量及相对应类型有不同,都不能赋值。

      (3)可选参数及剩余参数:

      比较函数兼容性的时候,可选参数与必须参数是可互换的。 源类型上有额外的可选参数不是错误,目标类型的可选参数在源类型里没有对应的参数也不是错误。

    当一个函数有剩余参数时,它被当做无限个可选参数。

      这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefinded

      (4)函数重载:

      对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

      (5)枚举:

      枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。

      (6)类:

      类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

    class Animal {
        feet: number;
        constructor(name: string, numFeet: number) { }
    }
    
    class Size {
        feet: number;
        constructor(numFeet: number) { }
    }
    
    let a: Animal;
    let s: Size;
    
    a = s;  // OK
    s = a;  // OK

      类的私有成员(private)和受保护(protected)成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

      (7)注意:

      目前为止,我们使用了“兼容性”,它在语言规范里没有定义。 在TypeScript里,有两种兼容性:子类型和赋值。 它们的不同点在于,赋值扩展了子类型兼容性,增加了一些规则,允许和any来回赋值,以及enum和对应数字值之间的来回赋值。

      语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即使在implementsextends语句也不例外。


    三、高级类型:

    (1)交叉类型:

      将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, Person & Serializable & Loggable同时是 Person  Serializable  Loggable。 就是说这个类型的对象同时拥有了这三种类型的成员。

      交叉类型的含义为:符合类型 A 和 B 的交叉类型的值,既符合类型 A,又符合类型 B。

      类比前文的联合类型,交叉类型可以认为是两个类型的交集。其内涵覆盖了原来两个集合的所有内涵。

    (2)自定义的类型保护:

      TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词

    function isFish(pet: Fish | Bird): pet is Fish {
        return (<Fish>pet).swim !== undefined;
    }

      pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。

      每当使用一些变量调用 isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

    (3)typeof的类型保护:

      这些* typeof类型保护*只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

    function padLeft(value: string, padding: string | number) {
        if (typeof padding === "number") {
            return Array(padding + 1).join(" ") + value;
        }
        if (typeof padding === "string") {
            return padding + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }

    (4)instanceof 类型保护:

      instanceof类型保护是通过构造函数来细化类型的一种方式。

      instanceof的右侧要求是一个构造函数,TypeScript将细化为:

    1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
    2. 构造签名所返回的类型的联合

    以此顺序。

    interface Padder {
        getPaddingString(): string
    }
    
    class SpaceRepeatingPadder implements Padder {
        constructor(private numSpaces: number) { }
        getPaddingString() {
            return Array(this.numSpaces + 1).join(" ");
        }
    }
    
    class StringPadder implements Padder {
        constructor(private value: string) { }
        getPaddingString() {
            return this.value;
        }
    }
    
    function getRandomPadder() {
        return Math.random() < 0.5 ?
            new SpaceRepeatingPadder(4) :
            new StringPadder("  ");
    }
    
    // 类型为SpaceRepeatingPadder | StringPadder
    let padder: Padder = getRandomPadder();
    
    if (padder instanceof SpaceRepeatingPadder) {
        padder; // 类型细化为'SpaceRepeatingPadder'
    }
    if (padder instanceof StringPadder) {
        padder; // 类型细化为'StringPadder'
    }

    (5)类型别名:(尽量少用,多用接口)

      类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。

    type Name = string;
    type NameResolver = () => string;
    type NameOrResolver = Name | NameResolver;
    function getName(n: NameOrResolver): Name {
        if (typeof n === 'string') {
            return n;
        }
        else {
            return n();
        }
    }

      类型别名可以像接口一样;然而,仍有一些细微差别。

    其一,接口创建了一个新的名字,可以在其它任何地方使用类型别名并不创建新名字—比如,错误信息就不会使用别名。 

    另一个重要区别是类型别名不能被 extends和 implements(自己也不能 extends和 implements其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名

    (6)字符串(/数字)字面量类型:

      允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。

    type Easing = "ease-in" | "ease-out" | "ease-in-out";
    class UIElement {
        animate(dx: number, dy: number, easing: Easing) {
            if (easing === "ease-in") {
                // ...
            }
            else if (easing === "ease-out") {
            }
            else if (easing === "ease-in-out") {
            }
            else {
                // error! should not pass null or undefined.
            }
        }
    }
    
    let button = new UIElement();
    button.animate(0, 0, "ease-in");
    button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

    (7)可辨识联合:

      你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:

    1. 具有普通的单例类型属性— 可辨识的特征
    2. 一个类型别名包含了那些类型的联合— 联合
    3. 此属性上的类型保护。
    interface Square {
        kind: "square";
        size: number;
    }
    interface Rectangle {
        kind: "rectangle";
         number;
        height: number;
    }
    interface Circle {
        kind: "circle";
        radius: number;
    }

      每个接口都有 kind属性但有不同的字符串字面量类型。 kind属性称做 可辨识的特征或 标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。

    重复属性的联合:

    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
    }

    (8)多态的this 类型:

      多态的 this类型表示的是某个包含类或接口的 子类型。 这被称做 F-bounded多态性。 它能很容易的表现连贯接口间的继承,

      由于这个类使用了 this类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。

    (9)索引类型:

      使用索引类型,编译器就能够检查使用了动态属性名的代码。

    function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
      return names.map(n => o[n]);
    }
    
    interface Person {
        name: string;
        age: number;
    }
    let person: Person = {
        name: 'Jarid',
        age: 35
    };
    let strings: string[] = pluck(person, ['name']); // ok, string[]

      编译器会检查 name是否真的是 Person的一个属性。 本例还引入了几个新的类型操作符。 首先是 keyof T, 索引类型查询操作符。 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合。正如:

      keyof Person是完全可以与 'name' | 'age'互相替换的。 不同的是如果你添加了其它的属性到 Person,例如 address: string,那么 keyof Person会自动变为 'name' | 'age' | 'address'

      T[K], 索引访问操作符。 在这里,类型语法反映了表达式语法。 这意味着 person['name']具有类型 Person['name'] — 在我们的例子里则为 string类型。 然而,就像索引类型查询一样,你可以在普通的上下文里使用 T[K],这正是它的强大所在。 你只要确保类型变量 K extends keyof T可以了。

    (10)映射类型:

      TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 例如,你可以令每个属性成为 readonly类型或可选的。

    (后续补充。。。)

  • 相关阅读:
    网化商城
    ITU R-REC-S 系列建议书分类
    UDLua
    libev简单使用
    Windows 审计日志 安全部分不刷新的解决办法
    sys.version_info
    mitmproxy 安装
    Python3 os.remove() 方法
    Python3 os.rename() 方法
    python3 unittest
  • 原文地址:https://www.cnblogs.com/marvintang1001/p/11828700.html
Copyright © 2020-2023  润新知