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
infer
The derived variable type isHello World!
(Hello World! Is a literal type and has no SpacesR
Save, 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 timeT
Is of typeHello World!
The type after extends becomesHello World!
This one has a spaceT
The 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!