preface

In addition to defining some basic types, some of the operators in TS can not be used to define complex types. The following will describe some common advanced operators in TS, and you are welcome to supplement them.

The keyword

extends

Syntax: T extends K

The extends extends does not extend to a class. Rather, it limits and constrains whether T can be assigned to K

Sometimes you can define generic types that don’t want to be any type at all. To extend certain classes, you can add generic constraints through the extends keyword.

const useCopyKeys = <T extends U.U>(target: T, source: U) => {
    for (let key in source) {
        target[key] = (source as T)[key];
    }
    return target;
};

const obj = { a: 1, b: 2, c: 3, d: 4 };
useCopyKeys(obj, { b: 10, c: 20 });
Copy the code

The above constraint states that the generic type T must contain U. If U has fields that do not exist in T, then an error occurs. In short, T does not contain U.

It can also be used for conditional judgment

Extends conditional distribution

For example, instantiate T extends U? X: Y, there is A feature, when the type of T | A | B C is joint type, will be resolved as conditional distribution, (A extends U? X : Y) | (B extends U ? X : Y) | (C extends U ? X, Y),

The following Exclude tool types Exclude all U in T

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

type E0= Exclude<string | number.number>; // string
Copy the code

The above is distributed as string extends Number, right? never : string| number extends number ? Never: number, so you get string.

in

The in keyword is used to enumerate types:

type Keys = 'status' | 'code';
type Response = {
  [p in Keys]: any;
};

// Equivalent to:
type Response = {
  status: any;
  code: any;
};
Copy the code

Note that:

If [P in number] is used to generate a numeric indexed type, it can also be an array:

Use the TypeScript built-in tool method Record as an example:

// Record implementation method
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

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

type Result = Record<number, Customer>; // type Result = { [x: number]: Customer; }

const varible: Result = [{ name: 'jac'.age: 111 }]; // OK
Copy the code

[{name: ‘jac’, age: 111}] is successful, equivalent to [0: {name: ‘jac’, age: 111}].

This is not the case with string indexes

type Result = Record<string, Customer>; // type Result = { [x: number]: Customer; }

const varible: Result = [{ name: 'jac'.age: 111 }]; // Error

const varible: Result = { info: { name: 'jac'.age: 111}};// OK
Copy the code

is

Syntax: prop is Type

Check whether prop is of Type

The is keyword is used to determine whether a parameter is of a certain type and return the corresponding Boolean type based on the result.

Check whether the isError parameter value is of type Error

export const ErrorBox = ({ error }: { error: unknown }) = > {
  if(error? .message) {return <Typography.Text type={"danger"} >{error? .message}</Typography.Text>;
  }
  return null;
}
Copy the code

The ErrorBox component passes an error object based on props. Error may be null or have a value. .message determines if there is a value and displays its own error message component if there is. But the error message ‘the object is of type “unknown”. ‘, but since nested TypeScript doesn’t do proper type determination, you can use the IS exact range.

// Type guard, shrink value to Error
const isError = (value: any): value is Error=> value? .message;export const ErrorBox = ({ error }: { error: unknown }) = > {
  if (isError(error)) {
    return <Typography.Text type={"danger"} >{error? .message}</Typography.Text>;
  }
  return null;
}
Copy the code

You can also see TypeScript keywords here.

infer

Infer infer variable types from being able to match the type that is closest to the type that extends can receive to the left. Infer tries to match the closest type.

Infer the location where the placeholder keyword appears: Usually at the following three positions.

  • Infer appears at the parameter type location of the function type following the extends condition statement.

  • Infer appears on the return value type of the function type that follows the extends condition statement.

  • Infer appears on the generic embodiment type of a type.

Note: Infer can only occur in the extends condition statement.

Infer P can accept any type

For example:

type InferParamType<T> = T extends (param: infer P) => any ? P : T;
Copy the code

Infer P is a new generic declared in a conditional judgment that infer the specific type of the generic type from the actual incoming type

The above utility type, InferParmaType, gets the parameter type of the function type. Here we use it:

// Tool type InferType
type InferParamType<T> = T extends (param: infer P) => any ? P : T;

interface Customer {
  custname: string;
  buymoney: number;
}

type FuncType = (param: Customer) = > string;

type InferResultType = InferParamType<FuncType>;
Copy the code

Finally, the type received by InferResultType is Customer:

Why is that?

When executing tool type InferParamType

equivalent to:

// type FuncType = (param: Customer) => string;

type InferParamType<FuncType> = FuncType extends (param: infer P) => any ? P : FuncType;
Copy the code

FuncType whether a type satisfies (param: Infer P) => any If so, return the variable P defined by infer; if not, return FuncType. The variable type P defined by infer can receive any type, that is, the type derived from infer. The type of Param is Customer, P and the receiver will be defined as Customer. Therefore, the FuncType type can meet the constraint conditions. So return P which is of type Customer.

Here’s another example that doesn’t work:

Redefine FuncType and add a nums parameter:

// Tool type InferType
// type InferParamType<T> = T extends (param: infer P) => any ? P : T;

type FuncType = (param: Customer,nums: number) = > string;

type InferResultType = InferParamType<FuncType>;
Copy the code

The type obtained by InferResultType is the type FuncType that was passed in:

FuncType is returned because multiple parameter function types do not satisfy fewer parameter function types.

(TS interview questions)

Implements a LeftTrim string type with left Spaces removed

type A = ' Hello World! ';
type B = LeftTrim<A>;
Copy the code

The answer:

Infer, and recursion.

type LeftTrim<T extends string> = T extends ` ${infer R}` ? LeftTrim<R> : T
Copy the code

inferThe derived variable type isHello World! (Hello World! Is a literal type and has no SpacesRSave, because the template string is preceded by another space, the condition will be satisfied the first time, so it will recurse the variableR(Hello World! ) Once again toLeftTrim, the second time into this timeTIs of typeHello World! The type after extends becomesHello World!This one has a spaceTThe type has no Spaces, so the constraint is not metT, so we get:

So remove the space on the right:

type LeftTrim<T extends string> = T extends `${infer R} ` ? LeftTrim<R> : T
Copy the code

typeof

The typeof operator can be used to determine whether the typeof data is a primitive type (number, string, Boolean,…). And object types.

interface User {
    name: string;
    useId: number;
}

const obj: User = { name: 'ssn'.useId: 888000 }
type TUser = typeof obj 
// type TUser = User
Copy the code

Typeof type protection

function greet(person: string | string[]) :string | string[] {
    if (typeof person === 'string') {
        return `Hello, ${person}! `;
    } else if (Array.isArray(person)) {
        return person.map(name= > `Hello, ${name}! `);
    }
    throw new Error('Unable to greet');
}
greet('World');          // 'Hello, World! '
greet(['Jane'.'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
Copy the code

Greet internally typeof determines the typeof the parameter passed to determine whether the corresponding logic outputs a string or an array of strings.

instance

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

The right-hand side of instanceof is required to be a constructor.

class Song {
  constructor(public title: string.public duration: number){}}class Playlist {
  constructor(public name: string.public songs: Song[]){}}function getItemName(item: Song | Playlist) {
  if(item instanceof Song) {
    return item.title;
  }
  return item.name;
}

const songName = getItemName(new Song('Wonderful Wonderful'.300000));
console.log('Song name:', songName) // Song name: Wonderful Wonderful 


const playlistName = getItemName(
new Playlist('The Best Songs'[new Song('The Man'.300000)));console.log('Playlist name:', playlistName); // Playlist name: The Best Songs
Copy the code

In the above code, the instanceof operator is used in the getItemName function to infer whether the passed item argument is of type Song or Playlist.

keyof

Syntax: keyof T

This operator can be used to get all known public property keys of type T, whose return type is the union type.

interface User {
    name: string;
    useId: number;
}
type UserKeys = keyof User; // type UserKeys = 'name' | 'useId'
Copy the code

If the property is defined as private:

interface User {
    name: string;
    private useId: number;
}
type UserKeys = keyof User; // type UserKeys = 'useId'
Copy the code

In TypeScript rules, if keyof any so at this point any contain type string | number | symbol.

Index access operation symbol T[K]

Grammar: T [K]

Similar to how object indexes are used in JS, except that JS returns the value of an object property, whereas TypeScript returns T

The type of the corresponding attribute K.

interface User {
    name: string;
    useId: number;
}

type UserNmaeType = User['name'] // string
Copy the code

The last

If you don’t understand or describe the above, please point out in the comment section. If you want to see how you are doing, you can do the 48 TS exercises. The following article also includes the answers and explanations.

Do these 48 TypeScript exercises and see how you get on with TS!