TypeScript advanced types and usage

1, Advanced type

& cross type

Cross type is to combine multiple types into one type. This allows us to stack existing types together into one type, which contains all the required types of features.

  • Syntax:   T & U

    Its return type must comply with   T   The type shall also comply with   U   type

  • Usage: suppose there are two interfaces: one is   Ant   Ant interface, one is   Fly   Flying interface, now there is a flying ant:

    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: 'Ants hey',
        weight: 0.2,
        flyHeight: 20,
        speed: 1,
    };

Union type (|)

Union types are very related to cross types, but they are completely different in use.

  • Syntax:   T | U

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

  • Usage: suppose to declare a data, which can be   string   Type, or   number   type

    let stringOrNumber:  string | number = 0
    
    stringOrNumber = ''

  Look at the following example, start   The argument type of the function is   Bird | Fish, then   start   Function, if you want to call directly, you can only call   Bird   and   Fish   All have methods, otherwise the compilation will report an error

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) {
    // It's OK to call layEggs, because both Bird and Fish have layEggs methods
    pet.layEggs();

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

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

start(bird);

start(fish);

2, Keywords

Type constraints (extensions)

Syntax: T extends K

The extensions here are not the inheritance of classes and interfaces, but the judgment and constraints on types, which means to judge whether T can be assigned to K

You can constrain incoming types in generics

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

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

// An error will be reported: Argument of type 'boolean' is not assignable to parameter of type 'string | number'
// copy(false)

You can also judge whether t can be assigned to U. if possible, return T; otherwise, return never

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

Type mapping (in)

It will traverse the key of the specified interface or the union type

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

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

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

Type predicate (is)

  • Syntax: parameterName is Type

    parameterName   It must be a parameter name from the current function signature to judge whether parametername is Type.

Specific application scenarios can follow the following code ideas:

After reading the example of union type, you may consider: if you want to   start   Function, call according to the situation   Bird   of   fly   Methods and   Fish   of   swim   Method, how to operate it?

The first thought may be to directly check whether the member exists, and then call:

function start(pet: Bird | Fish) {
    // It's OK to call layEggs, 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 as Fish).swim();
    }
}

However, it is troublesome to perform type conversion during judgment and call. You may want to write a tool function to judge:

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

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

function start(pet: Bird | Fish) {
    // It's OK to call layEggs, because both Bird and Fish have layEggs methods
    pet.layEggs();

    if (isBird(pet)) {
        (pet as Bird).fly();
    } else if (isFish(pet)) {
        (pet as Fish).swim();
    }
}

It seems a little concise, but when calling a method, we still need to carry out type conversion, otherwise we will still report an error. What good way is there to enable us to call the method directly after judging the type without type conversion?

OK, there must be, type predicate   is   It comes in handy

Initial code:

Or with   Type predicate is   Demonstrate the code in the example:

  • Usage:
    function isBird(bird: Bird | Fish): bird is Bird {
        return !!(bird as Bird).fly
    }
    
    function start(pet: Bird | Fish) {
        // It's OK to call layEggs, because both Bird and Fish have layEggs methods
        pet.layEggs();
    
        if (isBird(pet)) {
            pet.fly();
        } else {
            pet.swim();
        }
    };

    Whenever you use some variables to call   isFish   TypeScript   The variable is reduced to that specific type, as long as the type is compatible with the original type of the variable.

    TypeScript not only knows that pet is a Fish type in the if branch; It is also clear that in the else branch, it must not be Fish type, but Bird type

    Type to be inferred (infer)

    Can use   infer P   To mark a generic type, indicating that the generic type is a type to be inferred and can be used directly

    For example, the following example of obtaining function parameter types:

    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

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

    Another example of obtaining 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

    Judge whether T can be assigned to   (param: any) = > infer U, and infer the return value type as generic U. if it can be assigned, return the return value type P, otherwise return the incoming type

    Original type protection (typeof)

  • Syntax: typeof v === "typename"   or   typeof v !== "typename"
  • It is used to judge whether the data type is an original type (number, string, boolean, symbol) and protect the type

    'typename 'must be' number ',' string ',' boolean ', or' symbol '. But TypeScript does not prevent you from comparing with other strings, and the language does not recognize those expressions as type protected.

    Look at the following example,   print   The function will print different results according to the parameter type. How to judge whether the parameter is   string   still   number   And?

    function print(value: number | string) {
        // If it is a string type
        // console.log(value.split('').join(', '))
    
        // If it is of type number
        // console.log(value.toFixed(2))
    }

    There are two common judgment methods:

  • Depending on whether it contains   split   Attribute judgment yes   string   Type, including   toFixed   Method judgment is   number   type

    Disadvantages: type conversion is required for both judgment and call

  • Using type predicates   is

    Disadvantages: it's too troublesome to write a tool function every time

  • Usage: Here we are   typeof   It's time to show your skills
    function print(value: number | string) {
        if (typeof value === 'string') {
            console.log(value.split('').join(', '))
        } else {
            console.log(value.toFixed(2))
        }
    }

    After using typeof for type determination, TypeScript will reduce the variable to the specific type, as long as the type is compatible with the original type of the variable.

    Type protection (instanceof)

    And   typeof   Similar, but in different ways, instanceof   Type protection is a way to refine types through constructors.

    instanceof   The right side of the requires a constructor, TypeScript   Refine to:

  • Of this constructor   prototype   Property, if its type is not   any   Words
  • Construct the union of the types returned by the signature

    Or with   Type predicate is   Demonstrate the code in the example:

    Initial code:

    function start(pet: Bird | Fish) {
        // It's OK to call layEggs, 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 as Fish).swim();
        }
    }

    use   instanceof   Code after:

    function start(pet: Bird | Fish) {
        // It's OK to call layEggs, because both Bird and Fish have layEggs methods
        pet.layEggs();
    
        if (pet instanceof Bird) {
            pet.fly();
        } else {
            pet.swim();
        }
    }

    The same effect can be achieved

    Index type query operator (keyof)

  • Syntax: keyof T
  • For any type   T,   keyof T   The result is   T   Known on   Public attribute name   of   union

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

type PersonProps = keyof Person; // 'name' | 'age'

 

Here, keyof Person   The returned type is the same as the union type of 'name' | 'age', and can be completely replaced with each other

  • Usage: keyof   Only known on type can be returned   Public attribute name
    class Animal {
        type: string;
        weight: number;
        private speed: number;
    }
    
    type AnimalProps = keyof Animal; // "type" | "weight"

    For example, we often get a property value of an object, but we are not sure which property it is. We can use it at this time   extends   coordination   typeof   Limit the property name. The parameter passed in can only be the property 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')
    
    // An error will be reported: Argument of type '"gender"' is not assignable to parameter of type '"name" | "age"'
    // getPersonValue('gender')

    Index access operator (T[K])

  • Syntax: T[K]
  • be similar to   js   Using object indexes in, but   js   Is to return the value of the object property, and in   ts   Is returned in   T   Type of corresponding attribute P

  • 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"

    3, Mapping type

    Read only type (readonly < T >)

  • definition:
    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    }

    Used to   T   All properties of type are set to read-only.

  • Usage:
    interface Person {
        name: string
        age: number
    }
    
    const person: Readonly<Person> = {
        name: 'Lucy',
        age: 22
    }
    
    // Can't assign to 'name' because it is a read only property
    person.name = 'Lily'

    readonly   Read only, by   readonly   Marked properties can only be assigned in the declaration or in the constructor of the class, and then they will not be modifiable (i.e. read-only properties)

    Read only array (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>;
    }

    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' }]
    
    // An error will be reported: 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'

    Optional type (partial < T >)

    Used to   T   All properties of type are set to optional status, first through   keyof T, fetch type   T   All properties of,
    Then pass   in   Operator, and finally add  ?, Make the attribute optional.

  • definition:
    type Partial<T> = {
        [P in keyof T]?: T[P];
    }
  • Usage:
    interface Person {
        name: string
        age: number
    }
    
    // Error will be reported: Type '{}' is missing the following properties from type 'Person': name, age
    // let person: Person = {}
    
    // The new type returned after Partial mapping, name and age, have become optional attributes
    let person: Partial<Person> = {}
    
    person = { name: 'pengzu', age: 800 }
    
    person = { name: 'z' }
    
    person = { age: 18 }

    Required type (required < T >)

    and   Partial   The effect is opposite

    Used to   T   All properties of type are set to the required status. First, through   keyof T, fetch type   T   All properties of,
    Then pass   in   Operator, and finally after the attribute  ?  Add before  -, Make the attribute mandatory.

  • definition:
    type Required<T> = {
        [P in keyof T]-?: T[P];
    }
  • use:
    interface Person {
        name?: string
        age?: number
    }
    
    // After using the Required mapping, the new type returned, name and age, have become Required attributes
    // Error will be reported: type '{}' is missing the following properties from type 'required < person >': name, age
    let person: Required<Person> = {}

    Extract attributes (pick < T >)

  • definition:
    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    }

    from   T   Type as a new return type.

  • Usage: for example, when sending a network request, we only need to pass some attributes in the type   Pick   To achieve.
    interface Goods {
        type: string
        goodsName: string
        price: number
    }
    
    // As network request parameters, only goodsName and price are required
    type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>
    // Return type:
    // type RequestGoodsParams = {
    //     goodsName: string;
    //     price: number;
    // }
    const params: RequestGoodsParams = {
        goodsName: '',
        price: 10
    }

    Exclude attributes (omit < T >)

  • Definition: type omit < T, K extends keyof T > = pick < T, exclude < keyof T, k > >

    and   Pick   On the contrary, it is used to   T   Type, excluding some properties

  • Usage: for example, a cuboid has length, width and height, while a cube has the same length, width and height, so it only needs to be long, so it can be used at this time   Omit   The type of next generation cube

    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 }

    Extraction type (extract < T, u >)

  • Syntax: extract < T, u >

    extract   T   Medium can   assignment   to   U   Type of

  • Definition: type extract < T, u > = 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

    Exclusion type (exclude < T, u >)

  • Syntax: exclude < T, u >

    And   Extract   Contrary to the usage, from   T   Can be assigned to   Type of U

  • Definition: type exclude < T, u > = 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

    Attribute mapping (record < K, t >)

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

    Receive two generics, K   Must be assignable to   string | number | symbol   Type of, by   in   Operator pair   K   For traversal, the type of each attribute must be   T   type

  • Usage: for example, we want to   Person   Type array into object mapping, you can use   Record   To specify the type of 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
    })

    For example, when passing a parameter, if you want the parameter to be an object, but you don't know the specific type, you can use it   Record   As parameter type

    function doSomething(obj: Record<string, any>) {
    }

    Non nullable type (nonnullable < T >)

  • Definition: type nonnullable < T > = t extends null | undefined? never : T
  • Remove from T   null,undefined,never   Type, not culled   void,unknow   type

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

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

type T03 = NonNullable<{name?: string, age: number} | string[] | null | undefined>;  // {name?: string, age: number} | string[]

 

Constructor parameter type (constructorparameters < typeof T >)

Returns a class consisting of constructor parameter types   Tuple type

  • 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;
  • 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']

    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;
  • 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'
    }

    Function parameter type (parameters < T >)

    Gets the parameter type of the function   tuple

  • 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;
  • Usage:
    type FunctionType = (name: string, age: number) => boolean
    
    type FunctionParamsType = Parameters<FunctionType>  // [name: string, age: number]
    
    const params:  FunctionParamsType = ['Jack', 20]

    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;
  • use:
    type FunctionType = (name: string, age: number) => boolean | string
    
    type FunctionReturnType = ReturnType<FunctionType>  // boolean | string

    4, Summary

  • Advanced type

  • usagedescribe
    &Cross type, merging multiple types into one type, intersection
    |Union type, which combines multiple types into one type, can be any one of multiple types, and can be combined
  • keyword

    usagedescribe
    T extends UType constraint to determine whether T can be assigned to U
    P in TType mapping, traversing all types of T
    parameterName is TypeType predicate to judge whether the function parameter parameterName is type
    infer PThe type to be inferred can be used by marking type P with infer
    typeof v === "typename"Primitive type protection determines whether the data type is an primitive type (number, string, boolean, symbol)
    instanceof vType protection, which determines whether the type of data is a constructor   prototype   Attribute type
    keyofIndex type query operator, which returns the information known on the type   Public attribute name
    T[K]Index access operator, return   T   Type of corresponding attribute P
  • mapping type

    usagedescribe
    ReadonlyMake all attributes in T read-only
    ReadonlyArrayReturns a read-only array of type T
    ReadonlyMap<T, U>Returns a read-only Map composed of T and U types
    PartialMake all attributes in T optional types
    RequiredMake all attributes in T mandatory
    Pick<T, K extends keyof T>Extract some attributes from T
    Omit<T, K extends keyof T>Exclude some attributes from T
    Exclude<T, U>Eliminate the types that can be assigned to U from T
    Extract<T, U>Extract the types in T that can be assigned to U
    Record<K, T>Returns a type with a property name of K and a property value of T
    NonNullableEliminate null and undefined from T
    ConstructorParametersGets a tuple of T's constructor parameter types
    InstanceTypeGets the instance type of T
    ParametersGets a tuple of function parameter types
    ReturnTypeGets the return value type of the function

    Write it at the back

    If there is something wrong or not rigorous, you are welcome to put forward your valuable opinions. Thank you very much.

Tags: TypeScript ts

Posted on Wed, 24 Nov 2021 06:40:10 -0500 by john6384