Author: Xu Haiqiang
Ts built-in types
Partial
Make it optional
type Partial<T> = {
[P inkeyof T]? : T[P]; };Copy the code
Here’s a little explanation
Keyof T retrieves all attribute names of T, then in traverses, assigning values to P, and finally T[P] retrieves the corresponding attribute values.
Example:
interface People {name: string}
/ / toPartial<People> => {name? :string}
Copy the code
It is limited and can only handle one layer
interface People {
name: string;
person: {name: string; name1: string}}type NewPeople = Partial<People>
// error: Property 'name1' is missing in type ...
const jack: NewPeople = {
name: 'jack'.person: {
name: 'son'}}// How to solve recursion
type PowerPartial<T> = {
[U inkeyof T]? : T[U]extends object
? PowerPartial<T[U]>
: T[U]
};
Copy the code
Readonly
read-only
type Readonly<T> {readonly [P in keyof T]: T[P]}
Copy the code
Jack.person. name can be modified directly as in the Partial example above. You can also use Partial
type ReadonlyPartial<T> = { readonly [P inkeyof T]? : T[P] };Copy the code
Required
type Required<T> = {
[P inkeyof T]-? : T[P]; };Copy the code
The above -? Well, it makes sense to represent the alternatives, right? Remove to make this type mandatory. And that corresponds to a plus, right? This meaning is naturally related to -? Instead, it was used to make properties optional.
Pick
Take a set of properties of K from T
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Copy the code
Example:
type NewPerson = Pick<People, 'name'>; // { name: string; }
Copy the code
Exclude
Removes a set of attributes of U from T
type Exclude<T, U> = T extends U ? never : T;
// demo
type T = Exclude<1 | 2.1 | 3> / / = > 2
Copy the code
Exclude Extract
type Extract<T, U> = T extends U ? T : never;
type T = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' > // => // 'b'|'c'
Copy the code
Use a combination of Pick and Exclude
For example, the return type defined by the back-end interface is this, but we cannot change it directly
interface Api {
name: string;
age: number
}
// error: Types of property 'name' are incompatible.
interface CustomApi extends Api {
name: number;
}
// change
interface CustomApi1 extends Pick<Chicken, 'age'> {
name: number;
}
// If you Exclude all attributes, it will save you a lot of time
interface CustomApi2 extends Pick<Api, Exclude<keyof Api, 'name'>> {
name: number;
}
Omit Omit
interface CustomApi3 extends Omit<Api, 'name'> {
name: number;
}
Copy the code
Similar to Exclude effects and NonNullable, null | undefined ruled out
type NonNullable<T> = T extends null | undefined ? never : T;
// demo
type Test = '111' | '222' | null;
type NewTest = NonNullable<Test>; / / '111' | '222'
Copy the code
Omit
Not contain
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
// demo
type Foo = Omit<{name: string.age: number}, 'name'> // -> { age: number }
Copy the code
Record
Marks the key value type of the object
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// demo
const user: Record<'name'|'email'.string> = {
name: ' '.email: ' '
}
// Make it complicated
function mapObject<K extends string | number.T.U> (obj: Record<K, T>, f: (x: T) => U) :Record<K.U>;
// this is a simple implementation, otherwise ts(2391) is reported
function mapObject() :any {}
const names = { foo: "hello".bar: "world".baz: "bye" };
const lengths = mapObject(names, s= > s.length);
type newNames = typeof lengths // => { foo: number, bar: number, baz: number }
Copy the code
ReturnType
The solution
type ReturnType<T> = T extends(... args:any[]) => infer R ? R : any;
Copy the code
Infer R is used to declare a variable to carry the return value type of the function signature (inverse solution). Infer R is used to infer the return value type of the function signature. To understand infer, for example
// Reverse solve the Promise type
type PromiseType<T> = (args: any[]) = > Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
// demo
async function stringPromise() {
return "string promise";
}
type extractStringPromise = UnPromisify<typeof stringPromise>; // string
Copy the code
ReturnType demo
// demo
function TestFn() {
return 'test';
}
type Test = ReturnType<typeof TestFn>; // => string
Copy the code
So that’s pretty much what we can do with a PromiseType
type PromiseType<T extends Promise<any>> = T extends Promise<infer R> ? R : never;
// demo
type Test = PromiseType<Promise<string>> // => string
Copy the code
Combine it a little bit further
type PromiseReturnType<T extends() = >any> = ReturnType<T> extends Promise<
infer R
>
? R
: ReturnType<T>
async function test() {
return { a: 1.b: '2'}}type Test = PromiseReturnType<typeof test> // The type of Test is {a: number; b: string }
Copy the code
Parameters
Gets all the argument types of a function
Infer: The ReturnType from above learned about Infer. Here we can infer the source code and demo directly
type Parameters<T extends(... args:any) = >any> = T extends(... args: infer P) =>any ? P : never;
// demo
interface IPerson {name: string}
interface IFunc {
(person: IPerson, count: number) :boolean
}
type P = Parameters<IFunc> // => [IPerson, number]
const person1: P[0] = {
name: '1'
}
Copy the code
ConstructorParameters
Like Parameters
, ConstructorParameters take the ConstructorParameters of a class
type ConstructorParameters<T extends new(... args:any) = >any> = T extends new(... args: infer P) =>any ? P : never;
// demo
type DateConstrParams = ConstructorParameters<typeof Date> // => string | number | Date
// Add the Date constructor definition in the source code
interface DateConstructor {
new (value: number | string | Date) :Date;
}
Copy the code
Service combination and customization
1. Overrides properties sometimes
interface Test {
name: string;
age: number;
}
// error: Type 'string | number' is not assignable to type 'string'
interface Test2 extends Test{
name: Test['name'] | number
}
Copy the code
The implementation sets the value of the key in T to any and it doesn’t conflict
type Weaken<T, K extends keyof T> = {
[P in keyof T]: P extends K ? any : T[P];
}
interface Test2 extends Weaken<Test, 'name'>{
name: Test['name'] | number
}
Copy the code
Of course, the more extreme approach above could also be to exclude rewrite
interface Test2 extends Omit<Test, 'name'> {
name: Test['name'] | number
}
Copy the code
2. Combine types|
Convert to cross type&
type UnionToIntersection<U> = (U extends any
? (k: U) = > void
: never) extends ((k: infer I) = > void)? I :never
type Test = UnionToIntersection<{ a: string } | { b: number} >// => { a: string } & { b: number }
// But we can extrapolate an exception
type Weird = UnionToIntersection<string | number | boolean> // => never
// Because it is impossible to have a string and a number and true and false values.
Copy the code
One might have to understand Distributive Conditional types first, for example T extends U? X: Y, when T is A | B will be split into A extends U? X : Y | B extends U ? X: Y; Infer will infer multiple candidate types of variables of the same type into cross types. Here is an example:
// This is converted to a federated type
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string.b: string} >.// string
type T11 = Foo<{ a: string.b: number} >.// string | number
// This is converted to cross type
type Bar<T> = T extends { a: (x: infer U) = > void.b: (x: infer U) = > void}? U :never;
type T20 = Bar<{ a: (x: string) = > void.b: (x: string) = > void} >.// string
type T21 = Bar<{ a: (x: string) = > void.b: (x: number) = > void} >.// string & number
Copy the code
In conclusion:
type Result = UnionToIntersection<T1 | T2>; // => T1 & T2
Copy the code
- The first step:
(U extends any ? (k: U) => void : never)
theunion
Split into(T1 extends any ? (k: T1) => void : never) | (T2 extends any ? (k: T2)=> void : never)
That is, get(k: T1) => void | (k: T2) => void
; - The second step:
((k: T1) => void | (k: T2) => void) extends ((k: infer I) => void) ? I : never
According to the above, it can be inferred that I isT1 & T2
.
3. Convert arrays to unions
const ALL_SUITS = ['hearts'.'diamonds'.'spades'.'clubs'] as const; / / TS 3.4
type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs']
type Suit = SuitTuple[number]; // union type : 'hearts' | 'diamonds' | 'spades' | 'clubs'
Copy the code
Ts3.4 new syntax as const create immutable (constants) tuple type/array, so the TypeScript character type can be safely used narrow [‘ a ‘, ‘b’] rather than the more wide (‘ a ‘|’ b ‘) or even a string [] [] type
4. Merge the return value types of parameters
function injectUser() {
return { user: 1}}function injectBook() {
return { book: '2'}}const injects = [injectUser, injectBook]
Copy the code
// extend the UnionToIntersection at the second point
// Aggregate the union types
type Prettify<T> = [T] extends [infer U] ? { [K in keyof U]: U[K] } : never
type InjectTypes<T extends Array<() = > object>> = T extends Array<() = > infer P>
? Prettify<UnionToIntersection<P>>
: never
type result = InjectTypes<typeof injects> // The type of Test is {user: number, book: string}
Copy the code
It looks like a bunch, and we can take it apart step by step
type test = typeof injects; // => ((() => { user: number; }) | (() => { book: string; []}))
type test1<T> = T extends Array<() = > infer P> ? P : never
type test2 = test1<test> // =>{user: number; } | { book: string; }
// This step can be converted to the union type using the UnionToIntersection
type test3 = UnionToIntersection<test2>
type test4 = Prettify<test3> // =>{ user: number, book: string }
Copy the code
In fact, the above is too complicated, we can use another scheme
type User = ReturnType<typeof injectUser>
type Book = ReturnType<typeof injectBook>
type result = Prettify<User & Book>
Copy the code
I could end up with something like this
type User = ReturnType<typeof injectUser>
type Book = ReturnType<typeof injectBook>
type result = User | Book
Copy the code
5. Common built-in types combined with encapsulation
- PartialRecord
interface Model {
name: string;
age: number;
}
interfaceValidator { required? :boolean; trigger? :string;
}
// Define validation rules for the form
const validateRules: Record<keyof Model, Validator> = {
name: {required: true.trigger: `blur`}
// error: Property age is missing in type...
}
/ / to solve
type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>
const validateRules: PartialRecord<keyof Model, Validator> = {
name: {required: true.trigger: `blur`}}Copy the code
- DeepPartial
// Process array reprocessing objects
type RecursivePartial<T> = {
[P inkeyof T]? : T[P]extends (infer U)[] ? RecursivePartial<U>[] :
T[P] extends object ? RecursivePartial<T[P]> :
T[P];
};
Copy the code
- DeepRequired
type DeepRequired<T> = {
[P inkeyof T]-? : T[P]extends ((infer U)[]|undefined)? DeepRequired<U>[] : T[P]extends (object|undefined)? DeepRequired<T[P]> : T[P] }Copy the code
6. Pick outreadonly
field
type IfEquals<X, Y, A=X, B=never> =
(<T>() = > T extends X ? 1 : 2) extends
(<T>() = > T extends Y ? 1 : 2)? A : B;type ReadonlyKeys<T> = {
[P inkeyof T]-? : IfEquals<{ [Qin P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
}[keyof T];
type A = {
readonly a: string
b: number
}
type B = ReadonlyKeys<A> // => 'a'
Copy the code
ReadonlyKeys [Q in P] should be explained first. P it is a string, not a collection of strings, so [Q in P] is actually P. If you write {P:T[P]} directly, you get an object with a member variable “P”, and {[Q in P]:T[P]} gets the value of the variable P here (i.e., “a” or “b”), and it also brings back the readonly property. If you change ReadonlyKeys to type type like this
type ReadonlyKeys2<T> = {
[P inkeyof T]-? : { [Qin P]: T[P] }
};
Copy the code
So we’re going to get theta
ReadonlyKeys2 < A > {readonly a: {readonly a:string};
b: {b:number};
}
Copy the code
Then we’re going to call IfEquals. Here I need to point out that
()=>T extends X? The precedence of 1:2 is
()=>(T extends X? 1:2), in the case that T is a free variable, we are comparing whether X and Y are of the same type. When comparing two generic types, there is no way to get an exact value to evaluate them.
{
readonly a : (<T>() = >T extends {readonly a:string}?1 : 2) extends (<T>() = >T extends {a:string}?1 : 2)?never : 'a';
b : (<T>() = >T extends {b:number}?1 : 2) extends (<T>() = >T extends {b:number}?1 : 2)?never : 'b'; } ['a' | 'b']
Copy the code
– Readonly removes readonly for all attributes of T
()=>T extends {readOnly A :string}? 1:2 and
()=>T extends {a:string}?
()=>T extends {b:number}? 1:2 and
()=>T extends {b:number}? If a variable is not resolved, the extends part of Conditional Types must be identical to be equal. Therefore, the above type is reduced to
{
readonly a:'a';
b:never; } ['a' | 'b']
// js object value union type
Copy the code
Obviously never means nothing, so you get an ‘A’. If you read after help, welcome to like, if there is a better opinion and criticism, welcome to point out!