High-level types

The so-called high-level types are the language features introduced by TS to ensure the flexibility of the language. These features help us deal with complex and changing development scenarios.

Cross type

Cross typing is merging multiple types into one type. It contains all the required types of features. We mostly see the use of cross types in mixins and other places that don’t fit into the typical object-oriented model.

interface DogInterface {
  run(): void;
}
interface CatInterface {
  jump(): void;
}
let pet: DogInterface & CatInterface = { run() {}, jump(){}};Copy the code

The joint type

Union types indicate that a value can be one of several types. If a value is a union type, we can access only the members that are common to all types of that union type.

let a: number | string = "string"; // ok!
a = 1; // ok!
Copy the code
  • Literal union types

    Literal type: an exact variable provided by TS

    let b: 1 | "2" | true = 1; // ok!
    b = false; // error!
    Copy the code
  • Object association type

    class Dog implements DogInterface {
      run() {}
      eat(){}}class Cat implements CatInterface {
      jump() {}
      eat(){}}enum Master {
      Boy,
      Girl,
    }
    function getPet(master: Master) {
      let pet = master === Master.Boy ? new Dog() : new Cat();
      pet.eat(); // ok!
      pet.jump(); // error!
      return pet;
    }
    Copy the code
  • Distinguishable union types

    A type protection method that combines union and literal types. If a type is a combination of multiple types, and there are common attributes between the types, we can create type-protected blocks based on this common attribute.

    // Calculate the area of different figures
    
    interface Square {
      kind: "square";
      size: number;
    }
    
    interface Rectangle {
      kind: "rectangle";
      width: number;
      height: number;
    }
    
    function computedArea(shape: Square | Rectangle) {
      switch (shape.kind) {
        case "square":
          return shape.size * shape.size;
        case "rectangle":
          returnshape.width * shape.height; }}Copy the code

    At this point, I want to add a circle, there will be a hidden danger.

    interface Circle {
      kind: "circle";
      r: number;
    }
    function computedArea(shape: Square | Rectangle | Circle) {... } computedArea({kind: "circle".r: 1 }); // undefinded
    Copy the code

    There is no error at this point. There are two constraint schemes:

    • Specify an explicit return value type
      type Shape = Square | Rectangle | Circle;
      function computedArea(shape: Shape) :number {
        switch (shape.kind) {
          case "square":
            return shape.size * shape.size;
          case "rectangle":
            return shape.width * shape.height;
          case "circle":
            return Math.PI * shape.r ** 2; }}Copy the code
    • usingnevertype
      function computedArea(shape: Shape) {
        switch (shape.kind) {
          case "square":
            return shape.size * shape.size;
          case "rectangle":
            return shape.width * shape.height;
          case "circle":
            return Math.PI * shape.r ** 2;
          default:
            return ((e: never) = > {
              throw new Error(e); })(shape); }}Copy the code

      checkshapeWhether it isneverType; whencaseWhen it’s completely covered,shapeneverType; Otherwise, you need to complete the branch.

The index type

A common Javascript pattern is to pick a subset of properties from an object.

let Obj = {
  a: 1.b: 2.c: 3.d: 4};function getValues(obj: any, keys: string[]) {
  return keys.map((key) = > obj[key]);
}
getValues(Obj, ["a"."b"]);
getValues(Obj, ["e"."f"]);
Copy the code

How do we constrain keys to be an index in obj?

interface Obj {
  a: number;
  b: number;
  c: number;
  d: number;
}
let obj: Obj = {
  a: 1.b: 2.c: 3.d: 4};function getValues<T.K extends keyof T> (obj: T, keys: K[]) :T[K] []{
  return keys.map((key) = > obj[key]);
}
getValues(obj, ["a"."b"]);
Copy the code
  • keyof T: index type query operator

    For any type T, the result of keyof T is the union of known public attribute names on T.

    interface Obj {
      a: number;
      b: number;
      c: number;
      d: number;
    }
    let objProps: keyof Obj; // "a" | "b" | "c" | "d"
    Copy the code
  • T[K]: index access operator

    In this case, type syntax mirrors expression syntax.

    let type: Obj["a"]; // number
    Copy the code

Mapping type

Typescript provides a way to create new types from old types.

interface Flags {
  option1: boolean;
  option2: boolean;
}

type ReadonlyFlags = Readonly<Flags>;

/* type ReadonlyFlags = { readonly option1: boolean; readonly option2: boolean; } * /

/** * lib.es5.d.ts * Make all properties in T readonly type Readonly
      
        = { readonly [P in keyof T]: T[P]; }; * /
      
Copy the code
type PartialFlags = Partial<Flags>;
/* type PartialFlags = { option1? : boolean | undefined; option2? : boolean | undefined; } * /
Copy the code
type PickFlags = Pick<Flags, "option1">;
/* type PickFlags = { option1: boolean; } * /
type PickFlags = Pick<Flags, "option1" | "option2">;
Copy the code

Readonly, Partial, and Pick are homomorphic, but Record is not. Because Record does not require an input type to copy properties, it is not a homomorphism:

type twoStringProps = Record<"prop1" | "prop2".string>;
/* type twoStringProps = { prop1: string; prop2: string; } * /
Copy the code

Conditions in the

TypeScript 2.8 introduces conditional types, which can represent non-uniform types.

T extends U ? X : Y

Type X if T can be assigned to U, Y otherwise.

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : "object";
Copy the code
  • Exclude \ Extract \ NotNullable

    type T1 = Exclude<"a" | "b" | "c"."a" | "e">;
    // Exclude<"a", "a" | "e"> | Exclude<"b", "a" | "e"> | Exclude<"c", "a" | "e">
    // never | "b" | "c"
    // "b" | "c"
    
    type T2 = Extract<"a" | "b" | "c"."a" | "e">;
    // T2 = "a"
    
    type T3 = NotNullable<number | string | null>;
    // type T3 = string | number
    Copy the code
  • Type inference in conditional Types: ReturnType is now in the extends substatement of conditional types, allowing the infer statement to introduce a type variable to be inferred.

    type ReturnType<T> = T extends(... args:any[]) => infer R ? R : any;
    
    type T4 = ReturnType<() = > number>;
    Copy the code

The official implementation


/**
* Make all properties in T optional
*/
type Partial<T> = {
    [P inkeyof T]? : T[P]; };/**
* Make all properties in T required
*/
type Required<T> = {
    [P inkeyof T]-? : T[P]; };/**
* Make all properties in T readonly
*/
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;

/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;

/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;

/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends(... args:any) = >any> = T extends(... args: infer P) =>any ? P : never;

/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends new(... args:any) = >any> = T extends new(... args: infer P) =>any ? P : never;

/**
* Obtain the return type of a function type
*/
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;

/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends new(... args:any) = >any> = T extends new(... args:any) => infer R ? R : any;

/**
* Convert string literal type to uppercase
*/
type Uppercase<S extends string> = intrinsic;

/**
* Convert string literal type to lowercase
*/
type Lowercase<S extends string> = intrinsic;

/**
* Convert first character of string literal type to uppercase
*/
type Capitalize<S extends string> = intrinsic;

/**
* Convert first character of string literal type to lowercase
*/
type Uncapitalize<S extends string> = intrinsic;

Copy the code

The basic TypeScript family

  • Hello TypeScript (01) — Environment scaffolding
  • Hello Typescript (02) — Enum types
  • Hello Typescript (03) — Object type interface
  • Hello Typescript (04) — function-type interfaces, mixed-type interfaces, class interfaces
  • Hello Typescript (05) — functions
  • Hello Typescript (06) — Classes
  • Hello Typescript (07) — The relationship between classes and interfaces
  • Hello Typescript (08) — Generic
  • Hello Typescript (09) — Type inference, type compatibility, type protection
  • Hello Typescript (10) — Cross type, union type, index type, map type, conditional type