TypeScript advanced types and usage

This article introduces the usage scenarios of advanced TypeScript types in detail, which can provide some help for the daily use of TypeScript.

preface

This article has been included in   Github:   github.com/beichensky/…[1]   In the middle, pass by and point a Star

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) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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

//  Can only be passed in   string   perhaps   number
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
}

//  take   T   All properties of are converted to read-only types
type ReadOnlyType<T> = {
    readonly [P in keyof T]: T
}

// 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) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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

  • Usage:

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

function start(pet: Bird | Fish) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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   string   type
    // console.log(value.split('').join(', '))

    //  If it is   number   type
    // console.log(value.toFixed(2))
}

There are two common judgment methods:

  1. 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

  2. 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) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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) {
    //  call   layEggs   No problem, because   Bird   perhaps   Fish   Both   layEggs   method
    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, and then through   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 = {}

//  use   Partial   New type returned after mapping, name   and   age   All become optional properties
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, and then through   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
}

//  use   Required   New type returned after mapping, name   and   age   All 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 a network request parameter, only   goodsName   and   price   Can
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

Keywords: Javascript html5 html ts

Added by sv4rog on Tue, 23 Nov 2021 07:04:53 +0200