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:
-
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
-
Use the type predicate is
Cons: It’s too much trouble to write a utility function every time
- Usage: Here we go
typeof
It’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 constructor
prototype
Property, if its type is notany
if - 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:
keyof
Return 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 pass
Pick
To 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 to
Person
Type of array can be converted to an object mapRecord
To 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’s prototype
Attribute typeskeyof Index type query operator that returns known on the typePublic property name T[K] Index access operator that returns T
The 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