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)theunionSplit 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 : neverAccording 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 outreadonlyfield

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!