Let's finish what we started

高级类型

  1. https://www.tslang.cn/docs/handbook/advanced-types.html
  2. 交叉类型(Intersection Types),将多个类型合并为一个类型。

    例如, Person & Serializable & Loggable 同时是 Person 和 Serializable 和 Loggable。 就是说这个类型的对象同时拥有了这三种类型的成员。

    function extend<First, Second>(first: First, second: Second): First & Second {
       const result: Partial<First & Second> = {};
       for (const prop in first) {
          if (first.hasOwnProperty(prop)) {
             (result as First)[prop] = first[prop];
          }
       }
       for (const prop in second) {
          if (second.hasOwnProperty(prop)) {
             (result as Second)[prop] = second[prop];
          }
       }
       return result as First & Second;
    }
        
    class Person {
       constructor(public name: string) { }
    }
        
    interface Loggable {
       log(name: string): void;
    }
        
    class ConsoleLogger implements Loggable {
       log(name) {
          console.log(`Hello, I'm ${name}.`);
       }
    }
        
    const jim = extend(new Person('Jim'), ConsoleLogger.prototype);
    jim.log(jim.name);
    
  3. 联合类型表示一个值可以是几种类型之一。 我们用竖线( |)分隔每个类型,所以 number | string | boolean 表示一个值可以是 number, string,或 boolean。

    function padLeft(value: string, padding: string | number) {
        // ...
    }
        
    let indentedString = padLeft("Hello world", true); // errors during compilation
    

    如果一个值是联合类型,在不确定这个值的属于联合类型中那个具体的类型之前,我们只能访问此联合类型的所有类型里共有的成员。

  4. interface Bird {
        fly();
        layEggs();
    }
        
    interface Fish {
        swim();
        layEggs();
    }
        
    function getSmallPet(): Fish | Bird {
        // ...
    }
        
    let pet = getSmallPet();
    pet.layEggs(); // okay
    pet.swim();    // errors
    pet.layEggs(); // errors
    

    类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。

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

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

    // 'swim' 和 'fly' 调用都没有问题了
        
    if (isFish(pet)) {
        pet.swim();
    }
    else {
        pet.fly();
    }
    
    • 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}'.`);
    }
    
    • in 运算符

    n in x, 判断 n 是否为 x 的属性,返回 true or false。

    function move(pet: Fish | Bird) {
        if ("swim" in pet) {
            return pet.swim();
        }
        return pet.fly();
    }
    
    • 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) { // 类型细化为'SpaceRepeatingPadder'
       // ...
    }
    if (padder instanceof StringPadder) { // 类型细化为'StringPadder'
       // ...
    }
    
  5. 默认情况下,类型检查器认为 null 与 undefined 可以赋值给任何类型。 null 与 undefined 是所有其它类型的一个有效值。

    –strictNullChecks 标记

    1. 当你声明一个变量时,它不会自动地包含 null 或 undefined
    2. 可选参数会被自动地加上 | undefined
  6. identifier! 从 identifier 的类型里去除了 null 和 undefined(断言 identifier 不会存在 null 和 undefined)。

  7. 类型别名不会新建一个类型(其实是创建了一个新名字来引用那个类型)。 类型别名有时类似于接口,但可以命名原语,联合,元组以及您必须手动编写的任何其他类型。

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

    类型别名也可以是泛型

    type Tree<T> = {
        value: T;
        left: Tree<T>;
        right: Tree<T>;
    }
    

    类型别名不能出现在声明右侧的任何地方

    type Yikes = Array<Yikes>; // error
    
  8. 接口 vs. 类型别名

    1. 接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。
    2. 类型别名不能被 extends 和 implements(自己也不能 extends 和 implements 其它类型, Open–closed principle), 应尽量使用接口代替类型别名。
    3. 如果无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。

      type Name = string;
      type NameResolver = () => string;
      type NameOrResolver = Name | NameResolver;
      function getName(n: NameOrResolver): Name {
          if (typeof n === 'string') {
              return n;
          }
          else {
              return n();
          }
      }
      
  9. 字符串字面量类型允许你指定字符串必须的固定值。

    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
    

    字符串字面量类型还可以用于区分函数重载

  10. 数字字面量类型

    function foo(x: number) {
        if (x !== 1 || x !== 2) {
            //         ~~~~~~~
            // Operator '!==' cannot be applied to types '1' and '2'.
        }
    }
    
  11. 当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的。单例类型多数是指枚举成员类型和数字/字符串字面量类型。

  12. 可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合 或 代数数据类型。

    • 具有普通的单例类型属性— 可辨识的特征。
    • 一个类型别名包含了那些类型的联合— 联合。
    • 此属性上的类型保护。

      // kind属性称做 可辨识的特征或 标签。
      interface Square {
          kind: "square";
          size: number;
      }
      interface Rectangle {
          kind: "rectangle";
          width: number;
          height: number;
      }
      interface Circle {
          kind: "circle";
          radius: number;
      }
              
      type Shape = Square | Rectangle | Circle;
          
      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;
          }
      }
      

    当没有涵盖所有可辨识联合的变化时,需要进行完整性检查。

    1. 通过返回值是 number 还是 undefined 进行判断

      type Shape = Square | Rectangle | Circle | Triangle;
      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;
          }
          // should error here - we didn't handle case "triangle"
      }
      
    2. 使用 never 类型

      function assertNever(x: never): never {
          throw new Error("Unexpected object: " + x);
      }
      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;
              default: return assertNever(s); // error here if there are missing cases
          }
      }
      
  13. 多态的 this 类型表示的是某个包含类或接口的 子类型。 这被称做 F-bounded 多态性,它能很容易的表现连贯接口间的继承。

  14. keyof T, 索引类型查询操作符。 对于任何类型 T, keyof T 的结果为 T 上已知的公共属性名的联合。

        interface Person {
            name: string;
            age: number;
        }
            
        let personProps: keyof Person; // 'name' | 'age'
    
  15. T[K], 索引访问操作符。(eg:person[‘name’])

        function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
            return o[name]; // o[name] is of type T[K]
        }
            
        let name: string = getProperty(person, 'name');
        let age: number = getProperty(person, 'age');
        let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age'
    
  16. 映射类型

    interface PersonPartial {
        name?: string;
        age?: number;
    }
        
    interface PersonReadonly {
        readonly name: string;
        readonly age: number;
    }
    

    等同于

    interface Person {
        name: string;
        age: number;
    }
        
    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    }
    type Partial<T> = {
        [P in keyof T]?: T[P];
    }
        
    type PersonPartial = Partial<Person>;
    type ReadonlyPerson = Readonly<Person>;
    
    type Keys = 'option1' | 'option2';
    type Flags = { [K in Keys]: boolean };
        
    //type Flags = {
    //      option1: boolean;
    //      option2: boolean;
    //  }
    
  17. 同态类型,不会创建新的属性。

    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    } // 同态
    type Record<K extends string, T> = {
        [P in K]: T;
    } // 非同态,它们不会从它处拷贝属性修饰符
        
    type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
    
  18. 条件类型

    增加了表达非均匀类型映射的能力。条件类型根据表示为类型关系测试的条件选择两种可能类型之一。

    T extends U ? X : Y
    
  19. 预定义的有条件类型

    type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
    type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
        
    type T02 = Exclude<string | number | (() => void), Function>;  // string | number
    type T03 = Extract<string | number | (() => void), Function>;  // () => void
        
    type T04 = NonNullable<string | number | undefined>;  // string | number
    type T05 = NonNullable<(() => string) | string[] | null | undefined>;  // (() => string) | string[]
        
    function f1(s: string) {
        return { a: 1, b: s };
    }
        
    class C {
        x = 0;
        y = 0;
    }
        
    type T10 = ReturnType<() => string>;  // string
    type T11 = ReturnType<(s: string) => void>;  // void
    type T12 = ReturnType<(<T>() => T)>;  // {}
    type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;  // number[]
    type T14 = ReturnType<typeof f1>;  // { a: number, b: string }
    type T15 = ReturnType<any>;  // any
    type T16 = ReturnType<never>;  // any
    type T17 = ReturnType<string>;  // Error
    type T18 = ReturnType<Function>;  // Error
        
    type T20 = InstanceType<typeof C>;  // C
    type T21 = InstanceType<any>;  // any
    type T22 = InstanceType<never>;  // any
    type T23 = InstanceType<string>;  // Error
    type T24 = InstanceType<Function>;  // Error
    

    Exclude 类型是建议的 Diff 类型的一种实现。我们使用 Exclude 这个名字是为了避免破坏已经定义了 Diff 的代码,并且我们感觉这个名字能更好地表达类型的语义。我们没有增加 Omit 类型,因为它可以很容易的用 Pick> 来表示。

  20. 检查类型是裸类型参数的条件类型称为分布式条件类型。分布式条件类型在实例化期间自动分布在联合类型上。

    type BoxedValue<T> = { value: T };
    type BoxedArray<T> = { array: T[] };
    type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;
        
    type T20 = Boxed<string>;  // BoxedValue<string>;
    type T21 = Boxed<number[]>;  // BoxedArray<number>;
    type T22 = Boxed<string | number[]>;  // BoxedValue<string> | BoxedArray<number>;
    

    条件类型的分布属性可以方便地用于过滤联合类型:

    type Diff<T, U> = T extends U ? never : T;  // Remove types from T that are assignable to U
    type Filter<T, U> = T extends U ? T : never;  // Remove types from T that are not assignable to U
        
    type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
    type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
    type T32 = Diff<string | number | (() => void), Function>;  // string | number
    type T33 = Filter<string | number | (() => void), Function>;  // () => void
        
    type NonNullable<T> = Diff<T, null | undefined>;  // Remove null and undefined from T
        
    type T34 = NonNullable<string | number | undefined>;  // string | number
    type T35 = NonNullable<string | string[] | null | undefined>;  // string | string[]
        
    function f1<T>(x: T, y: NonNullable<T>) {
        x = y;  // Ok
        y = x;  // Error
    }
        
    function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
        x = y;  // Ok
        y = x;  // Error
        let s1: string = x;  // 严格模式下报错,正常不报错
        let s2: string = y;  // Ok
    }
    

    与映射类型结合使用:(不允许条件类型以递归方式引用它们)

    type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
    type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
        
    type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
    type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
        
    interface Part {
       id: number;
       name: string;
       subparts: Part[];
       updatePart(newName: string): void;
    }
        
    type T40 = FunctionPropertyNames<Part>;  // "updatePart"
    type T41 = NonFunctionPropertyNames<Part>;  // "id" | "name" | "subparts"
    type T42 = FunctionProperties<Part>;  // { updatePart(newName: string): void }
    type T43 = NonFunctionProperties<Part>;  // { id: number, name: string, subparts: Part[] }
    
    type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;  // Error
    
  21. 条件类型中的类型推断 在 extends 条件类型的子句中,现在可以具有 infer(infer 可用来推断函数参数或返回值)引入要推断的类型变量的声明。可以在条件类型的真分支中引用这样的推断类型变量。infer 可以为同一类型变量设置多个位置。

    type Unpacked<T> =
        T extends (infer U)[] ? U :
        T extends (...args: any[]) => infer U ? U :
        T extends Promise<infer U> ? U :
        T;
        
    type T0 = Unpacked<string>;  // string
    type T1 = Unpacked<string[]>;  // string
    type T2 = Unpacked<() => string>;  // string
    type T3 = Unpacked<Promise<string>>;  // string
    type T4 = Unpacked<Promise<string>[]>;  // Promise<string>
    type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string
    
    type TTuple = [string, number];
    type Res = TTuple[number]; // string | number,类型互转 tuple 转 union
    

    共变量位置中相同类型变量的多个候选项如何导致联合类型被推断:

    type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
    type T10 = Foo<{ a: string, b: string }>;  // string
    type T11 = Foo<{ a: string, b: number }>;  // string | number
    

    反变量位置中相同类型变量的多个候选者会导致交叉类型被推断:

    type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
    type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
    type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number
    

    当从具有多个调用签名的类型(例如,重载函数的类型)推断时,从最后一个签名(可能是最宽松的全部捕获的情况)进行推断。无法基于参数类型列表执行重载解析。

    declare function foo(x: string): number;
    declare function foo(x: number): string;
    declare function foo(x: string | number): string | number;
    type T30 = ReturnType<typeof foo>;  // string | number
    

    infer 对于常规类型参数,不可能在约束子句中使用声明,但通过擦除约束中的类型变量并改为指定条件类型,可以获得大致相同的效果:

    type ReturnType<T extends (...args: any[]) => infer R> = R;  // Error, not supported
    
    type AnyFunction = (...args: any[]) => any;
    type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any;
    

原文:大专栏  Let's finish what we started


上一篇:IDEA导入项目


下一篇:系统集成项目管理工程师知识点——前导图与活动之间的依赖关系