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: https://github.com/beichensky/Blog 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 should conform to both T type and U type
-
Usage: suppose there are two interfaces: one is Ant ant interface and the other is Fly interface. Now there is 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: '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 you declare a data, which can be either string or number
let stringOrNumber: string | number = 0 stringOrNumber = ''
Take another look at the following example. The parameter type of the start function is Bird | Fish. If you want to call the start function directly, you can only call the methods of Bird and Fish, 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 } // type ReadOnlyPerson = { // readonly name: Person; // readonly age: Person; // readonly gender: Person; // } type ReadOnlyPerson = ReadOnlyType<Person>
Type predicate (is)
-
Syntax: parameterName is Type
parameterName must be a parameter name from the current function signature. Judge whether parameterName is Type.
Specific application scenarios can follow the following code ideas:
After reading the union type example, you may consider: if you want to call Bird's fly method and Fish's swim method in the start function, what should you do?
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. The type predicate is comes in handy
- 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 isFish is called with some variables, TypeScript will reduce the variable 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)
Infor P can be used 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 incoming 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. The print function will print different results according to the parameter type. How to judge whether the parameter is string or number?
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:
-
The string type is determined according to whether the split attribute is included, and the number type is determined according to whether the toFixed method is included
Disadvantages: type conversion is required for both judgment and call
-
Use type predicate is
Disadvantages: it's too troublesome to write a tool function every time
- Usage: it's time for typeof to show its skill
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)
It is similar to typeof, but works differently. instanceof type protection is a way to refine types through constructors.
The right side of instanceof requires a constructor, and TypeScript will be refined as follows:
- The type of the prototype attribute of this constructor, if its type is not any
- Construct the union of the types returned by the signature
Let's also demonstrate with the code in the type predicate is 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(); } }
Code after using instanceof:
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, the result of keyof T is the union of known public attribute names on t
interface Person { name: string; age: number; } type PersonProps = keyof Person; // 'name' | 'age'
Here, the type returned by keyof Person is the same as the joint type of 'name' | 'age', and can be completely replaced with each other
- Usage: keyof can only return public property names known on a type
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. At this time, we can use extends and typeof to restrict the property name. The passed parameters 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]
It is similar to the method of using object index in js, except that the value of object attribute is returned in js, and the type of attribute P corresponding to T is returned in ts
- 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 set all properties of type T 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 is read-only. The attribute marked with readonly can only be assigned in the declaration or the constructor of the class, and then it will not be changed (i.e. read-only attribute)
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 >)
It is used to set all attributes of type T to optional status. First, get all attributes of type T through keyof T,
Then traverse through the in operator, and finally add?, after the attribute?, 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 >)
Contrary to Partial
It is used to set all attributes of type T as required. First, use keyof T to get all attributes of type T,
Then traverse through the in operator, and finally after the attribute? Add - to 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]; }
Extract some properties from the T type as the new return type.
- Usage: for example, when sending a network request, we only need to pass some attributes in the type, which can be realized through Pick.
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 > >
Contrary to Pick, it is used to exclude some attributes from T type
-
Usage: for example, a box has length, width and height, and a cube has the same length, width and height, so it only needs to be long. At this time, Omit can be used to generate the type of 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 the types in T that can be assigned to U
-
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 >
Contrary to Extract usage, types that can be assigned to U are excluded from T
-
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 generic types. K must be a type that can be assigned to string | number | symbol. K is traversed through the in operator. The type of each attribute must be T
- Usage: for example, if we want to convert an array of Person type into an object mapping, we 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 Record as the parameter type
function doSomething(obj: Record<string, any>) { }
Non nullable type (nonnullable < T >)
- Definition: type nonnullable < T > = t extends null | undefined? never : T
null, undefined and never types are excluded from T, and void and unknow n types are not excluded
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 the tuple type composed of constructor parameter 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;
- 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 tuple of the parameter types of the 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;
- 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
usage describe & 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
usage describe T extends U Type constraint to determine whether T can be assigned to U P in T Type mapping, traversing all types of T parameterName is Type Type predicate to judge whether the function parameter parameterName is type infer P The 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 v Type protection, which determines whether the data type is the prototype attribute type of the constructor keyof Index type query operator, which returns the public property name known on the type T[K] The index access operator returns the type of attribute P corresponding to T -
mapping type
usage describe Readonly Make all attributes in T read-only ReadonlyArray Returns a read-only array of type T ReadonlyMap<T, U> Returns a read-only Map composed of T and U types Partial Make all attributes in T optional types Required Make 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 NonNullable Eliminate null and undefined from T ConstructorParameters Gets a tuple of T's constructor parameter types 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 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.
If you like or help, welcome Star, which is also a kind of encouragement and support for the author