This article provides a detailed overview of TypeScript’s advanced types to help you with everyday TypeScript usage.

preface

This article has been posted on Github: github.com/beichensky/… In, pass by the point Star

1. Advanced types

Cross type (&)

Crossover typing is the merging of multiple types into a single type. This allows us to add existing types together into a single type that contains all the required features of the type.

  • Grammar: T & U

    The return type must conform to both T and U

  • Use: Suppose you have two interfaces: Ant and Fly, and now you have an Ant that can Fly:

interface Ant {
    name: string;
    weight: number;
}

interface Fly {
    flyHeight: number;
    speed: number;
}

// If any attribute is missing, an error will be reported
const flyAnt: Ant & Fly = {
    name: 'Hey ant'.weight: 0.2.flyHeight: 20.speed: 1};Copy the code

The joint type (|)

Union types are related to crossover types, but use them differently.

  • Grammar: T | U

    Its return type is any of the multiple types of the connection

  • Usage: Suppose you declare a piece of data, either of type string or number

letStringOrNumber:string | number = 0

stringOrNumber = ' '
Copy the code

Look at the example below, the start function parameter type is a Bird | Fish, then at the start function, want to call directly, it can call the Bird and Fish are method, otherwise the compiler complains

class Bird {
    fly() {
        console.log('Bird flying');
    }
    layEggs() {
        console.log('Bird layEggs'); }}class Fish {
    swim() {
        console.log('Fish swimming');
    }
    layEggs() {
        console.log('Fish layEggs'); }}const bird = new Bird();
const fish = new Fish();

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    / / error: the Property 'fly' does not exist on the type 'Bird | Fish'
    // pet.fly();

    / / error: the Property 'swim' does not exist on the type 'Bird | Fish'
    // pet.swim();
}

start(bird);

start(fish);
Copy the code

Two, keywords

Type constraints (extends)

Syntax: T extends K

Extends here is not an inheritance of a class or interface, but rather a judgment or constraint on a type, meaning that T can be assigned to K

You can impose constraints on incoming types in generics

const copy = (value: string | number) :string | number= > value

// Only string or number can be passed
copy(10)

/ / error: the Argument of type 'Boolean' is not assignable to the parameter of type 'string | number'
// copy(false)
Copy the code

It is also possible to determine whether T can be assigned to U and return T if so, otherwise return never

type Exclude<T, U> = T extends U ? T : never;
Copy the code

Type Mapping (in)

The key of the specified interface or the union type is traversed

interface Person {
    name: string
    age: number
    gender: number
}

// Convert all properties of T to read-only
type ReadOnlyType<T> = {
    readonly [P in keyof T]: T
}

// type ReadOnlyPerson = {
// readonly name: Person;
// readonly age: Person;
// readonly gender: Person;
// }
type ReadOnlyPerson = ReadOnlyType<Person>
Copy the code

Type predicate (is)

  • Syntax: parameterName is Type

    ParameterName must be a parameterName from the current function signature. Determine whether parameterName is of Type.

Specific application scenarios can be used following the following code ideas:

After looking at the union type example, you might wonder: What if you want to call Bird’s fly and Fish’s swim methods in the start function, depending on the situation?

The first thing that might come to mind is to check directly to see if the member exists, and then call it:

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    if ((pet as Bird).fly) {
        (pet as Bird).fly();
    } else if ((pet as Fish).swim) {
        (pet asFish).swim(); }}Copy the code

However, it is difficult to do this, judge and call the type conversion, so you might want to write a utility function to determine:

function isBird(bird: Bird | Fish) :boolean {
    return!!!!! (birdas Bird).fly;
}

function isFish(fish: Bird | Fish) :boolean {
    return!!!!! (fishas Fish).swim;
}

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    if (isBird(pet)) {
        (pet as Bird).fly();
    } else if (isFish(pet)) {
        (pet asFish).swim(); }}Copy the code

It looks a little bit cleaner, but when you call a method, you still have to cast it, otherwise you still get an error, so what’s a good way to call a method without casting it?

OK, there must be, and that’s where the type predicate is comes in

  • Usage:
function isBird(bird: Bird | Fish) :bird is Bird {
    return!!!!! (birdas Bird).fly
}

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    if (isBird(pet)) {
        pet.fly();
    } else{ pet.swim(); }};Copy the code

Whenever isFish is called with some variable, TypeScript reduces the variable to that specific type, as long as that type is compatible with the original type of the variable.

TypeScript not only knows that pet is Fish in the if branch; It also knows that in the else branch, it’s definitely not Fish, it’s definitely Bird

Type of infer

Infer P can be used to mark a generics, indicating that the generics are a type to be inferred and can be used directly

For example, here’s an example of getting the type of a function argument:

type ParamType<T> = T extends (param: infer P) => any ? P : T;

type FunctionType = (value: number) = > boolean

type Param = ParamType<FunctionType>;   // type Param = number

type OtherParam = ParamType<symbol>;   // type Param = symbol
Copy the code

Judge whether T can be assigned to (param: infer P) => any and infer the parameter to the generic type P. If it can be assigned, return the parameter type P; otherwise, return the passed type

Here’s another example of getting the return type of a function:

type ReturnValueType<T> = T extends (param: any) => infer U ? U : T;

type FunctionType = (value: number) = > boolean

type Return = ReturnValueType<FunctionType>;   // type Return = boolean

type OtherReturn = ReturnValueType<number>;   // type OtherReturn = number
Copy the code

Infer whether T can be assigned to (param: any) => infer U and infer the return value type to the generic type U. If yes, return the return value type P; otherwise, return the passed type

Primitive type protection

  • Grammar:typeof v === "typename"typeof v ! == "typename"

To determine whether the data type is a primitive type (number, string, Boolean, symbol) and type protection

“Typename” must be “number”, “string”, “Boolean” or “symbol”. But TypeScript doesn’t prevent you from comparing other strings; the language doesn’t recognize those expressions as type-protected.

In this example, the print function will print different results depending on the type of the argument. How can you tell if the argument is string or number?

function print(value: number | string) {
    // If the type is string
    // console.log(value.split('').join(', '))

    // If the type is number
    // console.log(value.toFixed(2))
}
Copy the code

There are two common ways to judge:

  1. String based on whether the split attribute is included, and number based on whether the toFixed method is included

    Disadvantages: Both the judgment and the call must be cast

  2. Use the type predicate is

    Cons: It’s too much trouble to write a utility function every time

  • Usage: Here we gotypeofIt’s time to show your hand
function print(value: number | string) {
    if (typeof value === 'string') {
        console.log(value.split(' ').join(', '))}else {
        console.log(value.toFixed(2))}}Copy the code

Using typeof for type determination, TypeScript reduces variables to that specific type, as long as that type is compatible with the original typeof the variable.

Type protection (instanceof)

Similar to Typeof, but in a different way, instanceof type protection is a way to refine types through constructors.

The right-hand side of instanceof requires a constructor, which TypeScript refines into:

  • For this constructorprototypeProperty, if its type is notanyif
  • Constructs a union of the types returned by the signature

Again, the code in the is example for the type predicate:

Original code:

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    if ((pet as Bird).fly) {
        (pet as Bird).fly();
    } else if ((pet as Fish).swim) {
        (pet asFish).swim(); }}Copy the code

Instanceof (instanceof)

function start(pet: Bird | Fish) {
    // Calling layEggs is ok because both Bird and Fish have layEggs methods
    pet.layEggs();

    if (pet instanceof Bird) {
        pet.fly();
    } else{ pet.swim(); }}Copy the code

Can achieve the same effect

Index type query operator (KEYof)

  • Grammar:keyof T

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

interface Person {
    name: string;
    age: number;
}

type PersonProps = keyof Person; // 'name' | 'age'
Copy the code

Here, keyof Person return type and the ‘name’ | ‘age’ joint type is same, can completely replace each other

  • Usage:keyofReturn only what is known on the typePublic property name
class Animal {
    type: string;
    weight: number;
    private speed: number;
}

type AnimalProps = keyof Animal; // "type" | "weight"
Copy the code

For example, it is common to get the value of an object property, but not sure which property it is. In this case, you can use extends with Typeof to restrict the name of the property to the name of the object

const person = {
    name: 'Jack'.age: 20
}

function getPersonValue<T extends keyof typeof person> (fieldName: keyof typeof person) {
    return person[fieldName]
}

const nameValue = getPersonValue('name')
const ageValue = getPersonValue('age')

/ / error: Argument of type '" gender "' is not assignable to the parameter of type '" name "|" age "'
// getPersonValue('gender')
Copy the code

Index access operator (T[K])

  • Grammar:T[K]

Js returns the value of the object property, but ts returns the type of the property P corresponding to T

  • Usage:
interface Person {
    name: string
    age: number
    weight: number | string
    gender: 'man' | 'women'
}

type NameType = Person['name']  // string

type WeightType = Person['weight']  // string | number

type GenderType = Person['gender']  // "man" | "women"
Copy the code

Mapping type

Read-only type (Readonly<T>)

  • Definition:
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
Copy the code

Use to set all properties of type T to read-only.

  • Usage:
interface Person {
    name: string
    age: number
}

const person: Readonly<Person> = {
    name: 'Lucy'.age: 22
}

Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'
Copy the code

Readonly Read-only. Attributes marked by readonly can only be assigned when declared or in the class constructor, and cannot be changed after that.

Read-only arrays (ReadonlyArray<T>)

  • Definition:
interface ReadonlyArray<T> {
    /** Iterator of values in the array. */
    [Symbol.iterator](): IterableIterator<T>;

    /** * Returns an iterable of key, value pairs for every entry in the array */
    entries(): IterableIterator<[number, T]>;

    /** * Returns an iterable of keys in the array */
    keys(): IterableIterator<number>;

    /** * Returns an iterable of values in the array */
    values(): IterableIterator<T>;
}
Copy the code

Variables can only be assigned when the array is initialized, and the array cannot be modified after that

  • Use:
interface Person {
    name: string
}

const personList: ReadonlyArray<Person> = [{ name: 'Jack' }, { name: 'Rose' }]

Property 'push' does not exist on type 'readonly Person[]'
// personList.push({ name: 'Lucy' })

// However, if the internal element is a reference type, the element itself can be modified
personList[0].name = 'Lily'
Copy the code

Optional types (Partial<T>)

To set all properties of type T to an optional state, first take all properties of type T by keyof T, then iterate through the in operator, and finally add? To make the property optional.

  • Definition:
type Partial<T> = {
    [P inkeyof T]? : T[P]; }Copy the code
  • Usage:
interface Person {
    name: string
    age: number
}

Type '{}' is missing the following properties from Type 'Person': name, age
// let person: Person = {}

// The new type returned with Partial mapping, with name and age becoming optional attributes
let person: Partial<Person> = {}

person = { name: 'pengzu'.age: 800 }

person = { name: 'z' }

person = { age: 18 }
Copy the code

Mandatory Type (Required<T>)

The opposite of Partial

To set all properties of type T to mandatory, first take all properties of type T by keyof T, then iterate through the in operator, and finally in the? To make an attribute mandatory, precede it with a -.

  • Definition:
type Required<T> = {
    [P inkeyof T]-? : T[P]; }Copy the code
  • Use:
interfacePerson { name? :stringage? :number
}

// With the new type returned by the Required mapping, both name and age become mandatory attributes
Type '{}' is missing the following properties from Type 'Required
      
       ': name, age
      
let person: Required<Person> = {}
Copy the code

Extract attributes (Pick<T>)

  • Definition:
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}
Copy the code

Extract some attributes from the T type as the new return type.

  • Use: for example, when we send a network request, we only need to pass some attributes of the type, can passPickTo implement.
interface Goods {
    type: string
    goodsName: string
    price: number
}

// As network request parameters, only need goodsName and price
type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>
// Return type:
// type RequestGoodsParams = {
// goodsName: string;
// price: number;
// }
const params: RequestGoodsParams = {
    goodsName: ' '.price: 10
}
Copy the code

Exclude attribute (Omit<T>)

  • Definition: type Omit

    = Pick

    >
    ,>
    ,>

    As opposed to Pick, it’s used to exclude certain attributes from the T type

  • Usage: For example, cuboid has length, width and height, while cube has the same length, width and height, so it only needs length, so you can use Omit to create cube type

interface Rectangular {
    length: number
    height: number
    width: number
}

type Square = Omit<Rectangular, 'height' | 'width'>
// Return type:
// type Square = {
// length: number;
// }

const temp: Square = { length: 5 }
Copy the code

Extract type (Extract<T, U>)

  • Extract
    ,>

    Extract the types in T that can be assigned to U

  • Type Extract

    = T extends U? T : never;
    ,>

  • Usage:

type T01 = Extract<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // "a" | "c"

type T02 = Extract<string | number | (() = > void), Function>;  // () => void
Copy the code

Exclusion type (Exclude<T, U>)

  • Syntax: Exclude
    ,>

    In contrast to Extract usage, remove from T types that can be assigned to U

  • Type Exclude

    = T extends U? never : T
    ,>

  • Usage:

type T00 = Exclude<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // "b" | "d"

type T01 = Exclude<string | number | (() = > void), Function>;  // string | number
Copy the code

Attribute mapping (Record<K, T>)

  • Definition:
type Record<K extends string | number | symbol, T> = {
    [P in K]: T;
}
Copy the code

Must receive two generic, K can be assigned to a string | number | symbol’s type, through the in operator to traverse the K, each type of the attribute must be a type T

  • Usage: let’s say we want toPersonType of array can be converted to an object mapRecordTo specify the type of the mapping object
interface Person {
    name: string
    age: number
}

const personList = [
    { name: 'Jack'.age: 26 },
    { name: 'Lucy'.age: 22 },
    { name: 'Rose'.age: 18},]const personMap: Record<string, Person> = {}

personList.map((person) = > {
    personMap[person.name] = person
})
Copy the code

For example, if you want the parameter to be an object, but you are not sure what type it is, you can use Record as the parameter type

function doSomething(obj: Record<string.any>) {}Copy the code

Non-null type (NonNullable<T>)

  • Definition:type NonNullable<T> = T extends null | undefined ? never : T

Delete null, undefined, and never from T; void, undefined, and never are not removed from T

type T01 = NonNullable<string | number | undefined>;  // string | number

type T02 = NonNullable<(() = > string) | string[] | null | undefined>;  // (() => string) | string[]

typeT03 = NonNullable<{name? :string.age: number} | string[] | null | undefined>;  // {name? : string, age: number} | string[]
Copy the code

Constructor parameter types (ConstructorParameters<typeof T>)

Returns the type of a tuple of constructor argument types in class

  • Definition:
/** * 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;
Copy the code
  • Use:
class Person {
    name: string
    age: number
    weight: number
    gender: 'man' | 'women'

    constructor(name: string, age: number, gender: 'man' | 'women') {
        this.name = name
        this.age = age;
        this.gender = gender
    }
}

type ConstructorType = ConstructorParameters<typeof Person>  // [name: string, age: number, gender: "man" | "women"]

const params: ConstructorType = ['Jack'.20.'man']
Copy the code

Instance type (InstanceType<T>)

Gets the return type of the class constructor

  • Definition:
/** * 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;
Copy the code
  • Use:
class Person {
    name: string
    age: number
    weight: number
    gender: 'man' | 'women'

    constructor(name: string, age: number, gender: 'man' | 'women') {
        this.name = name
        this.age = age;
        this.gender = gender
    }
}

type Instance = InstanceType<typeof Person>  // Person

const params: Instance = {
    name: 'Jack'.age: 20.weight: 120.gender: 'man'
}
Copy the code

Function parameter types (Parameters<T>)

Gets a tuple of the parameter types of a function

  • Definition:
/** * 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;
Copy the code
  • Usage:
type FunctionType = (name: string, age: number) = > boolean

type FunctionParamsType = Parameters<FunctionType>  // [name: string, age: number]

const params:  FunctionParamsType = ['Jack'.20]
Copy the code

Function return value type (ReturnType<T>)

Gets the return value type of the function

  • Definition:
/** * Obtain the return type of a function type */
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;
Copy the code
  • Use:
type FunctionType = (name: string, age: number) = > boolean | string

type FunctionReturnType = ReturnType<FunctionType>  // boolean | string
Copy the code

Four,

  • High-level types

    usage describe
    & Crossover type, merges multiple types into a single type, intersection
    \ The union type, which combines multiple types into a single type, can be any one of multiple types, union
  • The keyword

    usage describe
    T extends U Type constraint, determine whether T can be assigned to U
    P in T Type mapping, traversing all types of T
    parameterName is Type Type predicate: determines whether the function parameter parameterName is of Type
    infer P The type to be inferred can be used by using infer to mark type P
    typeof v === “typename” Primitive type protection, which determines if the data is of a primitive type (number,string,boolean,symbol)
    instanceof v Type protection, which determines whether the type of the data is the constructor’sprototypeAttribute types
    keyof Index type query operator that returns known on the typePublic property name
    T[K] Index access operator that returnsTThe type of the corresponding attribute P
  • Mapping type

    usage describe
    Readonly Make all properties in T read-only
    ReadonlyArray Returns a read-only array of type T
    ReadonlyMap<T, U> Return a read-only Map of the T and U types
    Partial Make all properties in T optional
    Required Make all properties in T mandatory
    Pick<T, K extends keyof T> Take some of the attributes from T
    Omit<T, K extends keyof T> Exclude some attributes from T
    Exclude<T, U> Remove from T types that can be assigned to U
    Extract<T, U> Extract the types in T that can be assigned to U
    Record<K, T> Returns a type whose property name is K and whose value is T
    NonNullable Remove null and undefined from T
    ConstructorParameters Gets a tuple of the constructor parameter types for T
    InstanceType Gets the instance type of T
    Parameters Gets a tuple of function parameter types
    ReturnType Gets the return value type of the function

Write in the back

If there is a wrong or not rigorous place to write, you are welcome to put forward valuable comments, thank you very much.

If you like or help, please welcome Star, which is also a kind of encouragement and support to the author