All of the following titles are from The Awesome typescript open source project. You can go to star😁.

πŸ” preface

Here are some things you can learn and apply:

  • The generic application

  • Union type, cross type use

  • Function overloading

  • tuples

  • Extends Distributed condition type and constraint

  • In the keyword

  • As assertions

  • Keyof keyword

  • Infer the keyword

  • -? The operator

  • -readonly Deletes the read-only symbol

  • The value of an attribute of type never in the loop is omitted

  • [number] gets all array type index values

  • When the type is crossed with any

  • What is a Flasy

  • Covariance inverter

Use of TypeScript built-in utility types

  • Omit

  • Pick

  • Required

  • Extract

  • Exclude

  • Parameters

  • ReturnType


The following tool type implementation and the logical analysis of the implementation of the problem feel wrong, or writing error, hope big guy help correct πŸ˜€, there is a better implementation and do not understand the place welcome comment area.

The first question

Why the following code prompts an error and how to resolve the problem.

type User = {
  id: number;
  kind: string;
};

function makeCustomer<T extends User> (u: T) :T {
  // Error (TS compiler: v4.4.2)
  // Type '{ id: number; kind: string; }' is not assignable to type 'T'.
  // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', 
  // but 'T' could be instantiated with a different subtype of constraint 'User'.
  return {
    id: u.id,
    kind: 'customer'}}Copy the code

Why the code above is wrong: The generic T is restricted only to the User type, not to the User type, so the return result should need to receive other type variables as well.

Solutions:

First, type T is compatible with type User

function makeCustomer<T extends User> (u: T) :T {
  // Error (TS compiler: v4.4.2)
  // Type '{ id: number; kind: string; }' is not assignable to type 'T'.
  // '{ id: number; kind: string; }' is assignable to the constraint of type 'T',
  // but 'T' could be instantiated with a different subtype of constraint 'User'.
  return {
    ...u,
    id: u.id,
    kind: 'customer'}; }Copy the code

Second, change the return value type to User

function makeCustomer<T extends User> (u: T) :User {
  // Error (TS compiler: v4.4.2)
  // Type '{ id: number; kind: string; }' is not assignable to type 'T'.
  // '{ id: number; kind: string; }' is assignable to the constraint of type 'T',
  // but 'T' could be instantiated with a different subtype of constraint 'User'.
  return {
    id: u.id,
    kind: 'customer'}; }function makeCustomer<T extends User> (u: T) :ReturnMake<T.User> {
  // Error (TS compiler: v4.4.2)
  // Type '{ id: number; kind: string; }' is not assignable to type 'T'.
  // '{ id: number; kind: string; }' is assignable to the constraint of type 'T',
  // but 'T' could be instantiated with a different subtype of constraint 'User'.
  return {
    id: u.id,
    kind: 'customer'}; }type ReturnMake<T extends User, U> = {
  [K in keyof U as K extends keyof T ? K : never]: U[K];
};

makeCustomer({ id: 18584132.kind: '888'.price: 99 });
Copy the code

1, ReturnMake tool type, accept T, U two generics, T constraint User,

Select * from User where User [K] is the key (s) and User [K] is the key (s).

The second question

In this example, we want parameters A and B to be of the same type, i.e. both a and B are of type number or string. When their types are inconsistent, the TS type checker automatically prompts the corresponding error message.

conditionsfunction f(a: string | number, b: string | number) {
  if (typeof a === 'string') {
    return a + ':' + b; // no error but b can be number!
  } else {
    return a + b; // error as b can be number | string}}Copy the code

Workaround: function overloading

function f(a: string, b: string) :string;
function f(a: number, b: number) :number;
function f(a: string | number, b: string | number) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a + ':' + b;
  } else {
    return a + b; 
  }
}

f(2.3); // Ok
f(1.'a'); // Error
f('a'.2); // Error
f('a'.'b'); // Ok
Copy the code

Using function overloading When calling a function, the function type f is defined in sequence, and internally, typeof is used to determine the corresponding logic of the type A and b.

The third question

How to define a SetOptional tool type that allows optional properties for a given key? The following is an example:

type Foo = {
	a: number; b? :string;
	c: boolean;
}

// Test case
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

// type SomeOptional = {
// a? : number; // This property has been made optional
// b? : string; // Keep the same
// c: boolean;
// }
Copy the code

SetOptional tool type implementation:

Omit Omit Omit Omit Omit Omit Omit Omit Omit
type SetOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

/ / the second
type SetOptionalOmit<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
Copy the code

SetOptional tool type: accept two generic types, T as the target type, K as the specified keys, K needs to be restricted to T keys, K

& Partial > :
,>

Omit anything and Omit anything. ** * Omit anything. First of all, Omit < T, K > = > Omit < Foo, 'a' | 'b' > = > {c: Boolean} * Partial < Pick < T, K > > here is the nested, let's look at a Pick < T, K > * 2. Pick < T, K > = > Pick < Foo, 'a' | 'b' > = > {a: number, b? : string } => * 3.Partial<{ a: number, b? : string}> All become optional => {a? : number, b? {c: Boolean} & {a? : number, b? : string} * 5. : number, b? : string , c: boolean} */
Copy the code

The second SetOptionalOmit operation is roughly the same as the first. The above steps are: 2 -> 1 -> 3 -> 4 -> 5.

After implementing the SetOptional tool type, you can also be interested in implementing a SetRequired tool type that makes the corresponding property for the specified keys mandatory.

Implement the SetRequired tool method:

/ / the first
type SetRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

/ / the second
type SetRequiredOmit<T, K extends keyof T> = Pick<T, K> & Required<Omit<T, K>>;
Copy the code

The Required utility type, which makes all interface type attributes mandatory.

The fourth question

The effect of Pick

is to Pick out the subproperties of a type into subtypes that contain some of the properties of that type.
,>

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room".completed: false
};
Copy the code

So how do you define a ConditionalPick tool type, support, according to the specified Condition Condition to generate new type corresponding to the use of the sample is as follows:

interface Example {
	a: string;
	b: string | number;
	c: () = > void;
	d: {};
}

// Test case:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
Copy the code

ConditionalPick ConditionalPick ConditionalPick ConditionalPick ConditionalPick

type ConditionalPick<V, T> = {
  [K in keyof V as V[K] extends T ? K : never]: V[K];
};
Copy the code

1, in keyof traversal V generic;

If V[K] is true, then return the current K iterated through; otherwise, never.

Returning never is considered a non-existent type by default in the TypeScript compiler, which means that no K is filtered and the corresponding value is V[K].

Tools like TypeScript internal tools implement tool methods Extract and Exclude by returning never.

type Extract<T, U> = T extends U ? T : never;

type Exclude<T, U> = T extends U ? never : T;
Copy the code

The fifth problem

AppendArgument adds an argument of the specified type to the existing function type. The new argument is named x, which will be the first argument of the new function type. The specific usage example is as follows:

type Fn = (a: number, b: string) = > number
type AppendArgument<F, A> = // Your implementation code

type FinalFn = AppendArgument<Fn, boolean> 
// (x: boolean, a: number, b: string) => number
Copy the code
  1. useParameters+ ReturnTypeTool type implementation:
type Fn = (a: number, b: string) = > number

type AppendArgument<F extends(... args:any) = >any, T> = (x: T, ... args: Parameters
       ) = > ReturnType<F>;

type FinalFn = AppendArgument<Fn, string>; 
// type FinalFn = (x: string, a: number, b: string) => number
Copy the code

AppendArgument is in the tool type

1, the generic type F is the type of the function that requires the parameter x, F is constrained by the type of the function, T is the type specified by the parameter x, and returns a new function type.

2, x parameter type is T… Args remaining parameter types use the Parameters utility type to get the array type of the F generic parameter type, and the ReturnType utility type to get the ReturnType of the F function type.

  1. useinferway
type AppendArgument<F extends(... args:any) = >any, T> = F extends (
  ...args: infer P
) => infer Return
  ? (x: T, ... args: P) = > Return
  : never;

type FinalFn = AppendArgument<Fn, boolean>; 
// type FinalFn = (x: boolean, a: number, b: string) => number
Copy the code

Infer takes the parameter type P and returns a Return type, and then returns a new function X with parameter T… The args parameter is of type P reserved for the previous derivation, and the Return value is Return.

The sixth question

Define a NativeFlat tool type that supports flat (flattening) of array types. The specific usage example is as follows:

type NaiveFlat<T extends any[] > =// Your implementation code

// Test case:
type NaiveResult = NaiveFlat<[['a'], [['b'.'c']], ['d']] >/ / NaiveResult results: "a" | "b" | | "c" "d"
Copy the code

To write recursively:

type NaiveFlat<T extends any[]> = T extends (infer P)[] ? P extends any[]? NaiveFlat<P> : P :never;

type NaiveResult = NaiveFlat<[['a'], [['b'.'c']], ['d']] >;// type NaiveResult = "a" | "b" | "c" | "d"
Copy the code

NaiveFlat implementation above

1. Firstly, we need to use the infer keyword in the constraint conditions to deduce the array type imported from T and save the array type with P.

Whether P type 2, 3 yuan nested constraints to type any [] if the array is still continue to recursive traversal call NaiveFlat < P > and incoming P, P type does not meet any [], return to the last complete flat P type so get the final joint type “a” | “b” | | “c” “d”.

Personal process understanding:

Take the [[‘a’], [[‘b’, ‘c’]], [‘d’]] values as an example

1, type of P is deduced for the first time to get to (” a “) | [[” b “, “c”]] | (” d “), satisfy the constraints;

2. If P is constrained by any[], then there is still an array, so P is passed recursively.

3, the second infer P P type is deduced for “a” | / “b”, “c” | “d”, once again, at this point in the road, conditional statements, joint type for naked, will be distributed, go first, ‘a’ logic, not satisfied with any [] to return to the ‘a’;

4, go through the ‘a’ to ‘b’, ‘c’], or meet any [] to continue recursion, return to get = > ‘a’ | ‘b’ | ‘c’.

In 5, last to ‘d’, = > ‘a’ | ‘b’ | ‘c’ | ‘d’.

Alternatively, if you have a fixed two-dimensional array, try this:

type NaiveFlat<T extends any[]> = T[number] [number];

const testArr = [['a'], ['b'.'c'], ['d']].const testArrType = typeof testArr; // string[][]
type NaiveResult = NaiveFlat<[['a'], ['b'.'c'], ['d']] >;// type NaiveResult = "a" | "b" | "c" | "d"
[number] take the median of the array as key,numberIs the array subscript ["a""|""b"."c""|""d"]
Copy the code

T[number][number] can be interpreted as a type expression of a two-dimensional array => T[][]. In TypeScript, the type [number] represents the middle of the array as the key, and number is the array subscript. So T corresponds to the [number] [” a “] | (” b “, “c”) | [” d “], T [number] [number] is “a” | “b” | | “c” “d”.

Number 7

Define an EmptyObject type using a type alias so that only EmptyObject assignments are allowed:

type EmptyObject = {} 

// Test case
const shouldPass: EmptyObject = {}; // It can be assigned normally
const shouldFail: EmptyObject = { // A compilation error will occur
  prop: "TS"
}
Copy the code

EmptyObject tool type implementation:

type EmptyObject = {
  [K in keyof any] :never;
};

// Test case
const shouldPass: EmptyObject = {}; // It can be assigned normally
const shouldFail: EmptyObject = { // A compilation error will occur
  prop: "TS"
}
Copy the code

EmptyObject type in keyof any [K] is equivalent to [K in string | number | symbol], all the object properties corresponding to the type is set to never.

Pay attention to is the index to the object type is string | number | symbol.

After testing the test case of EmptyObject type, let’s change the type definition of the following takeSomeTypeOnly function so that its parameters only allow strictly values of type SomeType. The specific usage example is as follows:

type SomeType =  {
  prop: string
}

// Change the type definition of the following function so that its arguments only allow strictly values of type SomeType
function takeSomeTypeOnly(x: SomeType) { return x }

// Test case:
const x = { prop: 'a' };
takeSomeTypeOnly(x) // Can be called normally

const y = { prop: 'a'.addditionalProp: 'x' };
takeSomeTypeOnly(y) // A compilation error will occur
Copy the code

Concrete implementation:

type Exclusive<T1, T2> = {
  [K in keyof T1]: K extends keyof T2 ? T1[K] : never;
};

function takeSomeTypeOnly<T extends SomeType> (x: Exclusive<SomeType, T>) {
  return x;
}

takeSomeTypeOnly({ prop: 'a' }); // OK

takeSomeTypeOnly({ prop: 'a'.addditionalProp: 'x' }) // A compilation error will occur
Copy the code

Traverses the SomeType type, leaving only the common attributes of the SomeType type and the parameter type T passed in, and the common attribute type takes the corresponding attribute type of SomeType. Unshared attributes are set to never exclude, which removes all attributes other than prop.

The eighth problem

Defines the NonEmptyArray utility type used to ensure that data is not an empty array.

type NonEmptyArray<T> = // Your implementation code

const a: NonEmptyArray<string> = [] // A compilation error will occur
const b: NonEmptyArray<string> = ['Hello TS'] // Non-empty data, normal use
Copy the code

NonEmptyArray tool type implementation:

type NonEmptyArray<T> = [T, ...T[]];

const a: NonEmptyArray<string> = [] // Error
const b: NonEmptyArray<string> = ['Hello TS'] // OK
Copy the code

[T,…T[]] Make sure the first entry is T, […T[]], which is the remaining array type.

Question 9

Defines a JoinStrArray utility type that concatenates string array types using the specified Separator Separator. The specific usage example is as follows:

type JoinStrArray<Arr extends string[], Separator extends string> = // Your implementation code

// Test case
type Names = ["Sem"."Lolo"."Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, ""> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐ ️"> / / "Sem ⭐ ️ Lolo ⭐ ️ Kaquko"
Copy the code

JoinStrArray Tool type:

type JoinStrArray<
  Arr extends string[],
  Separator extends string
> = Arr extends [infer A, ...infer B]
  ? `${A extends string ? A : ' '}${B extends [string.string[]]?`${Separator}${JoinStrArray<B, Separator>}`
      : ' '}`
  : ' ';
Copy the code

JoinStrArray method: Arr generic must be bound to string[] and Separator must be bound to string.

1. Arr was constrained by [infer A,…infer B], and the type of the first index A was obtained by infer keyword, and the type of the rest array was B.

If the constraint is satisfied, then the concatenate character, the concatenate character uses the template variable, first check whether A (i.e. the first index) constraint string, otherwise return empty string;

3, a, B, C, D [string,…string[]], meaning that there are more indexes. If it does, use the Separator symbol and recursively call the JoinStrArray tool type method. The Arr generic type is again B and the Separator generic type remains unchanged. Subtract, take each item of the array until the array is empty.

Initially, if the Arr does not satisfy the constraint, it returns an empty string.

The first ten questions

Implements a Trim utility type used to whitespace string literal types. The specific usage example is as follows:

type Trim<V extends string> = // Your implementation code

// Test case
Trim<' semlinker '>
//=> 'semlinker'
Copy the code

Trim tool type implementation:

type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;
type TrimRight<V extends string> = V extends `${infer R} ` ? TrimRight<R> : V;

type Trim<V extends string> = TrimLeft<TrimRight<V>>;

// Test case
type Result = Trim<' semlinker '>
//=> 'semlinker'
Copy the code

Infer removes Spaces using TS template strings.

Two tooltype methods need to be defined, Trim split into TrimLeft and TrimRight, one to remove left whitespace and the other to remove right.

Removing Spaces is used in template strings mainly through extends and infer. If you remove left Spaces, add a space to the left (${infer R}**) and then the mapping type can be recursive.

The eleventh problem

Implements an IsEqual utility type that compares whether two types are equal. The specific usage example is as follows:

type IsEqual<A, B> = // Your implementation code

// Test case
type E0 = IsEqual<1.2>; // false
type E1 = IsEqual<{ a: 1 }, { a: 1} >// true
type E2 = IsEqual<[1], [] >;// false
Copy the code

The IsEqual tool type implements:

type IsEqual<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false
Copy the code

The never and union types need to be considered here, so tuples are used for processing comparisons.

IsEqual tool type. If [A] is bound to [B] and [B] is bound to [A], they are equal, otherwise they are not equal.

Question 12

Implements a Head utility type that gets the first type of an array type. The specific usage example is as follows:

type Head<T extends Array<any> > =// Your implementation code

// Test case
type H0 = Head<[]> // never
type H1 = Head<[1] >/ / 1
type H2 = Head<[3.2] >/ / 3
Copy the code

Head tool type implementation:

type Head1<T extends Array<any>> = T extends [infer H, ...T[]] ? H : never;

// Test case
type H0 = Head<[]> // never
type H1 = Head<[1] >/ / 1
type H2 = Head<[3.2] >/ / 3
type H3 = Head<["a"."b"."c"] >// "a"
type H4 = Head<[undefined."b"."c"] >// undefined
type H5 = Head<[null."b"."c"] >// null
Copy the code

Infer the type of the first item of the array through the keyword, H saves the type, if the generic type T satisfies the constraint, return the inferred first item H, otherwise never,… T[] fetches the remaining array.

Article 13 questions

Implement a Tail utility type that gets the rest of the array types except the first type. The specific usage example is as follows:

type Tail<T extends Array<any> > =// Your implementation code

// Test case
type T0 = Tail<[]> / / []
type T1 = Tail<[1.2] >/ / [2]
type T2 = Tail<[1.2.3.4.5] >// [2, 3, 4, 5]
Copy the code

Tail tool type implementation:

type Tail1<T extends Array<any>> = T extends [infer H, ...infer R] ? R : never;

// Test case
type T0 = Tail<[]>; / / []
type T1 = Tail<[1.2>;/ / [2]
type T2 = Tail<[1.2.3.4.5>;// [2, 3, 4, 5]
Copy the code

The implementation is similar to problem 12.

Question 14

Implements an Unshift utility type that adds the specified type E as the first element to the T array type. The specific usage example is as follows:

type Unshift<T extends any[], E> =  // Your implementation code

// Test case
type Arr0 = Unshift<[], 1>; / / [1]
type Arr1 = Unshift<[1.2.3].0>; // [0, 1, 2, 3]
Copy the code

Unshift implementation method:

type Unshift<T extends any[], E> = [E, ...T];

// Test case
type Arr0 = Unshift<[], never>; / / [1]
type Arr1 = Unshift<[1.2.3].0>; // [0, 1, 2, 3]
Copy the code

Create an array with the first item of type E and the rest using… T connection.

Question 15

Implements a Shift utility type that removes the first type in a T-array type. The specific usage example is as follows:

type Shift<T extends any[] > =// Your implementation code

// Test case
type S0 = Shift<[1.2.3] >type S1 = Shift<[string.number.boolean] >Copy the code

Shift tool type implementation:

type Shift<T extends any[]> = T extends [infer A, ...infer B] ? B : [];

// Test case
type S0 = Shift<[1.2.3>;/ / [2, 3]
type S1 = Shift<[string.number.boolean>;// [number, boolean]
type S2 = Shift<[never>;/ / []
Copy the code

. Infer B removes the set after the first term and saves the type using the variable B. If the constraint is met, return the remaining parameter type, which is B.

Question 16

Implement a Push utility type that adds the specified type E as the last element to the T array type. The specific usage example is as follows:

type Push<T extends any[], V> = // Your implementation code

// Test case
type Arr0 = Push<[], 1> / / [1]
type Arr1 = Push<[1.2.3].4> // [1, 2, 3, 4]
Copy the code

Push implementation:

type Push<T extends any[], V> = [...T, V]; // Your implementation code

// Test case
type Arr0 = Push<[], 1> / / [1]
type Arr1 = Push<[1.2.3].4> // [1, 2, 3, 4]
Copy the code

The implementation of the Push tool type is similar to the implementation of Unshift in Question 14.

The 17th title

Implements an Includes tool type that determines whether the specified type E is included in the T array type. The specific usage example is as follows:

type Includes<T extends Array<any>, E> = // Your implementation code

type I0 = Includes<[], 1> // false
type I1 = Includes<[2.2.3.1].2> // true
type I2 = Includes<[2.3.3.1].1> // true
Copy the code

Includes tool types:

type Includes<T extends any[], U> = U extends T[number]?true : false;

// Test case
type I0 = Includes<[], 1> // false
type I1 = Includes<[2.2.3.1].2> // true 
type I2 = Includes<[2.3.3.1].1> // true
Copy the code

T here [number] can return T understand the type of the array elements, such as the incoming generic T for [2, 2, 3, 1), then T [number] is resolved as: 2 | 2 | 3 | 1.

18 the topic

Implements a UnionToIntersection tool type that converts a union type to an intersection type. The specific usage example is as follows:

type UnionToIntersection<U> = // Your implementation code

// Test case
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number} >// { name: string; } & { age: number; }
Copy the code

UnionToIntersection tool type

export type UnionToIntersection<Union> = (
  Union extends unknown ? (distributedUnion: Union) = > void : never
) extends (mergedIntersection: infer Intersection) => void
  ? Intersection
  : never;

// Test case
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number} >// { name: string; } & { age: number; }
Copy the code

1. Extends Unknown is always true and goes to distribution by default

2, declare A function of type A that takes Union, i.e. (distributedUnion: Union) => void. This function is bound to mergedIntersection: mergedIntersection: Infer Intersection) => void.

3. Function A returns the Intersection of the infer Intersection statement if it can inherit function B; otherwise, it returns never.

Here is also designed to a knowledge point: ** distributed condition type, ** condition type characteristics: distributed condition type. When used with union types (only for the union type to the left of extends), distributed condition types are automatically distributed to union types. For example, T extends U? X, Y, T type | A | B C, will be resolved as (A extends U? X : Y) | (B extends U ? X : Y) | (C extends U ? X, Y).

The infer statement is known to occur only in the extends substatement. However, in a covariant position, multiple candidate types of a variable of the same type are inferred to be joint types:

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
Copy the code

In contravariant positions, multiple candidate types of the same type are inferred as intersecting types:

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

Related links:

Type inference in conditional types

On covariant and contravariant

Question 19

Implements an OptionalKeys utility type that gets optional properties declared in object types. The specific usage example is as follows:

type Person = {
  id: string;
  name: string;
  age: number;
  from? :string; speak? :string;
};

type OptionalKeys<T> = // Your implementation code
type PersonOptionalKeys = OptionalKeys<Person> // "from" | "speak"
Copy the code

The OptionalKeys tool type implements:

type OptionalKeys<T> = {
  [P inkeyof T]-? :undefined extends T[P] ? P : never;
}[keyof T];

// Test case
type PersonOptionalKeys = OptionalKeys<Person>; // "from" | "speak"
Copy the code

For example, Peson:

1. All Person attributes will be iterated first. The extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends

2. On the right, check whether undefined is bound to the current key value. If so, the current attribute type is the key name.

In TypeScript, optional attributes are implicitly added with a undefined type, such as from? Is the string | is undefined

3, {… } [T] keyof take key value, because the id, the age, the name is never the attribute types, the value will be ignored, because never is a type does not exist, so only the from, speak the attribute’s value is “from” | “speak” coalition return type.

Question 20

Implements a Curry utility type that implements the Curryization of function types. The specific usage example is as follows:

type Curry<
  F extends(... args:any[]) = >any,
  P extends any[] = Parameters<F>, 
  R = ReturnType<F> 
> = // Your implementation code

type F0 = Curry<() = > Date>; // () => Date
type F1 = Curry<(a: number) = > Date>; // (arg: number) => Date
type F2 = Curry<(a: number, b: string) = > Date>; // (arg_0: number) => (b: string) => Date
Copy the code

Curry tool type implementation:

type Curry<
  F extends(... args:any[]) = >any,
  P extends any[] = Parameters<F>,
  R = ReturnType<F>
> = P extends [infer A, ...infer B]
  ? B extends[]?(arg: A) = > R
    : (arg: A) = > Curry<(. args: B) = > R>
  : () = > R;

// Test case
type F0 = Curry<() = > Date>; // () => Date
type F1 = Curry<(a: number) = > Date>; // (arg: number) => Date
type F2 = Curry<(a: number, b: string) = > Date>; // (arg_0: number) => (b: string) => Date
Copy the code

F is the type of function to be currified;

P obtains the parameter set F through Paramters.

R retrieves the return value of F function type by ReturnType;

Logical analysis:

Infer A: [infer A,…infer B]

2. Extends is used to determine whether P is satisfied with [infer A,…infer B] and if not, return () => R, indicating no parameters.

3. If there are one or more parameters, recursion continues.

First of all… Infer B needs to judge whether the termination condition is constrained and [].

Args: A => R;

Otherwise, create A function of type A and return Curry<(… (arg: A) => Curry<(arg: A) => Curry<(arg: A) => Curry<(arg: A) => Curry<(arg: A) => R> Args: B) => R>.

Problem 21

Implements a Merge utility type that merges two types into a new type. Keys of the SecondType will override Keys of the FirstType. The specific usage example is as follows:

type Foo = { 
   a: number;
   b: string;
};

type Bar = {
   b: number;
};

type Merge<FirstType, SecondType> = // Your implementation code

const ab: Merge<Foo, Bar> = { a: 1.b: 2 };
Copy the code

The Merge tool type implements:

interface Foo {
  b: number
}

interface Bar {
  a: number;
  b: string
}

type Merge<FirstType, SecondType> = {
  [K in keyof (FirstType & SecondType)]: K extends keyof SecondType
  ? SecondType[K]
  : K extends keyof FirstType
  ? FirstType[K]
  : never;
};

// Test case
type Obj = Merge<Foo, Bar> // { a: number ; b: string }
Copy the code

Note: merge attributes, the latter type overrides the former.

Logical analysis:

Cross FirstType and SecondType and iterate over each of their attributes.

2. If the current attribute name is in SecondType, the current attribute value in SecondType is used.

3. If the current attribute name is of type FirstType, the current attribute value of type First is used.

4. Otherwise, the value is never.

Other solutions:

Incorporate Omit built-in tool types

type Merge <FirstType, SecondType> = Omit<FirstType, keyof SecondType> & SecondType;
type Obj = Merge<Foo, Bar> // { a: number ; b: string }
const ab: Obj = { a: 1.b: "1" };
Copy the code

1. Delete the existing FirstType and SecondType attributes

2. Cross the previous result with SecondType to get the combined result.

Problem 22

Implement a RequireAtLeastOne tool type that creates a type with at least one given key, leaving the rest as is. The specific usage example is as follows:

typeResponder = { text? :() = > string; json? :() = > string; secure? :boolean;
};

type RequireAtLeastOne<
    ObjectType,
    KeysType extends keyof ObjectType = keyof ObjectType,
> = // Your implementation code

// Indicates that the current type contains at least a 'text' or 'json' key
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
    json: () = > '{"message": "ok"}'.secure: true
};
Copy the code

The RequireAtLeastOne tool type implements:

type RequireAtLeastOne<
  ObjectType,
  KeysType extends keyof ObjectType = keyof ObjectType
> = KeysType extends keyof ObjectType
  ? ObjectType & { [K inKeysType]-? : ObjectType[K] } :never;



// Indicates that the current type contains at least a 'text' or 'json' key
const responder: RequireAtLeastOne<Responder, 'text' | 'json'> = {
    json: () = > '{"message": "ok"}'.secure: true
};

// @ts-expect-error error due to lack of either 'text' or 'json
const responder2: RequireAtLeastOne<Responder, 'text' | 'json'> = {
    secure: true
};
Copy the code

1. The given Keys type needs to be bound to ObjectType;

2. If the Keys of the given KeysType are in ObjectType, create a new type, iterate over the KeysType as Key, and -? Character, change optional to mandatory, type ObjectType[K], and cross type ObjectType with the new type created. The union type is distributed in the extends condition, so the union type is returned;

3. Otherwise, return never.

Problem 23

Implements a RemoveIndexSignature tool type that removes index signatures from existing types. The specific usage example is as follows:

interface Foo {
  [key: string] :any;
  [key: number] :any;
  bar(): void;
}

type RemoveIndexSignature<T> = // Your implementation code

type FooWithOnlyBar = RemoveIndexSignature<Foo>; //{ bar: () => void; }
Copy the code

The RemoveIndexSignature tool type implements:

type RemoveIndexSignature<T> = {
  [K in keyof T as string extends K
    ? never
    : number extends K
    ? never
    : K]: T[K];
};

// Test case
type FooWithOnlyBar = RemoveIndexSignature<Foo>; //{ bar: () => void; }
Copy the code

1. Traverse T and use AS assertion to realize judgment filtering on K;

2, the current key if meet string | number directly returns the current property never filtering;

3, otherwise take the current K, the current K value type is T[K].

Problem 24

Implements a Mutable utility type that removes readonly modifiers for all or some properties of an object type. The specific usage example is as follows:

type Foo = {
  readonly a: number;
  readonly b: string;
  readonly c: boolean;
};

type Mutable<T, Keys extends keyof T = keyof T> = // Your implementation code

const mutableFoo: Mutable<Foo, 'a'> = { a: 1.b: '2'.c: true };

mutableFoo.a = 3; // OK
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.
Copy the code

Mutale tool type implementation:

type Mutable<T, Keys extends keyof T = keyof T> = {
  -readonly [K in Keys]: T[K];
} & Omit<T, Keys>;

const mutableFoo: Mutable<Foo, 'a'> = { a: 1.b: '2'.c: true };

// Test case
mutableFoo.a = 3; // OK
mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.
Copy the code

Keys: -readonly delete the read-only symbol;

2, Omit

return a new type of Keys property in T, and finally form a crossover type with the previous result.
,>

Problem 25

Implements an IsUnion utility type that determines whether the specified type is a union type. The specific usage example is as follows:

type IsUnion<T, U = T> = // Your implementation code

type I0 = IsUnion<string | number>; // true
type I1 = IsUnion<string | never>; // false
type I2 = IsUnion<string | unknown>; // false
Copy the code

IsUnion tool type implementation method:

type IsUnion<T, U = T> = T extends any ? ([U] extends [T] ? false : true) : never; // Your implementation code

// Test case
type I0 = IsUnion<string | number>; // true
type I1 = IsUnion<string | never>; // false
type I2 = IsUnion<string | unknown>; // false
Copy the code

1. T extends any ensures that it is always true.

2. When a union type T is written as [T], it becomes a common type. Extends is not distributed.

So [U] extends [T] must be no if it is an associative type.

Such as the incoming string | nuber type

// Will be distributed first
type IsUnion<string | number, U = string | number> = 
string extends ? any ? ([string | number]) extends [string]?false : true) : never
| number extends ? ([string | number]) extends [string]?false :true) : never 
// => true | true => true


string | neverIt's going to be reduced tonever
string | unknown= > unknown
Copy the code

Problem 26

Implements an IsNever utility type to determine whether the specified type is of never type. The specific usage example is as follows:

type IsNever<T> = // Your implementation code
type I0 = IsNever<never> // true
type I1 = IsNever<never | string> // false
type I2 = IsNever<null> // false
Copy the code

IsNever tool type implementation:

type IsNever<T> = [T] extends [never]?true : false;

// Test case
type II0 = IsNever<never> // true
type II1 = IsNever<never | string> // false
type II2 = IsNever<null> // false
type II3 = IsNever<{}> // false
type II4 = IsNever<[]> // false
type II5 = IsNever<[] | never> // false
Copy the code

1. [T] and [never] are tuples. As packing types, the combined type will not be distributed.

2. The never type cannot extend the never type, but never[] can extend never[].

Problem 27

Implements a Reverse utility type that reverses the position of elements in a tuple type and returns the array. The first element of the tuple becomes the last, and the last element becomes the first.

type Reverse<
  T extends Array<any>,
  R extends Array<any> = []
> = // Your implementation code

type R0 = Reverse<[]> / / []
type R1 = Reverse<[1.2.3] >/ / [3, 2, 1)
Copy the code

Reverse tool type implementation:

type Reverse<T extends Array<any>> = 
  T extends [infer First, ...infer Rest]
  ? [...Reverse<Rest>, First]
  : [];

// Test case
type R0 = Reverse<[]> / / []
type R1 = Reverse<[1.2.3] >/ / [3, 2, 1)
type R2 = Reverse<[1.2.3.4.5] >// [5, 4, 3, 2, 1]
Copy the code

The recursion method is adopted. Each recursion puts the First item First at the end and expands the recursion result.

Problem 28

Implement a Split utility type that splits the string containing the Delimiter based on the given Delimiter. Can be used to define the return value type of the string.prototype. split method. The specific usage example is as follows:

type Item = 'semlinker,lolo,kakuqo';

type Split<
	S extends string, 
	Delimiter extends string,
> = // Your implementation code

type ElementType = Split<Item, ', '>; // ["semlinker", "lolo", "kakuqo"]
Copy the code

Split tool type implementation:

type Item = 'semlinker,lolo,kakuqo';

export type Split<
  S extends string,
  Delimiter extends string
> = S extends `${infer Head}${Delimiter}${infer Tail}`
  ? [Head, ...Split<Tail, Delimiter>]
  : S extends Delimiter
  ? []
  : [S];

// Test case
type ElementType = Split<Item, ', '>; // ["semlinker", "lolo", "kakuqo"]
type ElementType2 = Split<'a|b|c||d'.'|'>; // ["a", "b", "c", "", "d"]
type ElementType3 = Split<'abcdef'.' '>; // ["a", "b", "c", "d", "e", "f"]
Copy the code

${infer Head}${Delimiter}${infer Tail} mapping type is used to infer a string.

${infer “semlinker”}${infer “lolo,kakuqo”} ${infer “lolo,kakuqo”} S extends Delimiter Handles the case where Delimiter is blank.

Problem 29

Implements a ToPath utility type that converts property access (. Or []) paths to tuples. The specific usage example is as follows:

type ToPath<S extends string> = // Your implementation code

ToPath<'foo.bar.baz'> //=> ['foo', 'bar', 'baz']
ToPath<'foo[0].bar.baz'> //=> ['foo', '0', 'bar', 'baz']
Copy the code

ToPath tool type implementation:

// Split each item with
type IndexSignature<T> = T extends `${infer H}[${infer M}] [${infer R}] `
  ? [H, M, ...IndexSignature<` [${R}] `>]
  : T extends `${infer F}[${infer L}] `
  ? [F, L]
  : [T];

// Check if array has ''
type NonSpace<T extends string[]> = T extends [infer H, ...infer R]
  ? R extends string[]? Hextends ' '
      ? [...NonSpace<R>]
      : [H, ...NonSpace<R>]
    : never
  : T;

// NonSpace and IndexSignature are combined
type ToPath<S extends string> = S extends `${infer H}.${infer R}`
  ? [...NonSpace<IndexSignature<H>>, ...ToPath<R>]
  : NonSpace<IndexSignature<S>>;

// Test case
type TT0 = ToPath<'foo.bar.baz'> //=> ['foo', 'bar', 'baz']  
type TT1 = ToPath<'foo[0].bar[0][1][2][3].car'>; // => ["foo", "0", "bar", "0", "1", "2", "3", "car"]
Copy the code

1, IndexSignature tool type to handle. To split, and recursively put the children of each item into a tuple;

2, IndexSignature finish processing such as foo [0] = > [1] get ` ` [” foo “, “0”, “”,” 1 “);

NonSpace handles empty strings in the array of values returned by IndexSignature tool types.

4, ToPath as delimiter. Split string, multiple items are concatenated and recursive, otherwise directly processed and returned.

Article 30 the topic

Refine the Chainable type definition so that TS can successfully infer the type of the result variable. The option method is used to extend the current object’s type to get the correct type after the get method is called.

declare const config: Chainable

type Chainable = {
  option(key: string.value: any) :any
  get(): any
}

const result = config
  .option('age'.7)
  .option('name'.'lolo')
  .option('address', { value: 'XiaMen' })
  .get()

type ResultType = typeof result  
// The expected ResultType type is:
/ / {
// age: number
// name: string
// address: {
// value: string
/ /}
// }
Copy the code

Chainable type implementation:

declare const config: Chainable;

type Simplify<T> = {
  [P in keyof T]: T[P];
};

type Chainable<T = {}> = {
  option<V, S extends string>(
    key: S,
    value: V
  ): Chainable<
    T & {
      [P in keyof { S: S } as `${S}`]: V;
    }
  >;
  get(): Simplify<T>;
};


const result = config
  .option('age'.7)
  .option('address', { name: 'Leslie' })
  .get();
  
  
type ResultType = typeof result;
/ / = > {
// age: number;
// address: {
// name: string;
/ /};
// }
Copy the code

Config can be chain-called, which is reminiscent of the js idea of return this. Therefore, the return value of opiton should be a new Chainable, using the added attribute type as the T of the next Chainable.

1. The option return type in the Chainable definition is a new Chainable, treating the added property as T for the next Chainable.

2. Get returns T directly from a Chainable.

Problem 31

Implement a Repeat utility type that repeats the T type and returns the new type as a tuple, based on the value of the type variable C. The specific usage example is as follows:

type Repeat<T, C extends number> = // Your implementation code

type R0 = Repeat<0.0>; / / []
type R1 = Repeat<1.1>; / / [1]
type R2 = Repeat<number.2>; // [number, number]
Copy the code

Repeat tool type implementation:

type Repeat<T, C extends number, A extends any[] = []> = A['length'] extends C
  ? A
  : Repeat<T, C, [...A, T]>;

// Test case
type R0 = Repeat<0.0>; / / []
type R1 = Repeat<1.1>; / / [1]
type R2 = Repeat<number.2>; // [number, number]
Copy the code

1, A is to receive A new type returned in tuple form by repeating T type according to the number of C;

2. Determine whether the array length of A meets C;

3. If not, continue to add T types that need to be repeated;

4, otherwise return the result of type A.

Problem 32

RepeatString Implements a RepeatString utility type that repeats type T based on the value of type variable C and returns the new type as a string. The specific usage example is as follows:

type RepeatString<
  T extends string,
  C extends number,
> = // Your implementation code

// Test case
type S0 = RepeatString<"a".0>; / /"
type S1 = RepeatString<"a".2>; // 'aa'
type S2 = RepeatString<"ab".3>; // 'ababab'
Copy the code

RepeatString tool type implementation:

type RepeatString<
  T extends string,
  C extends number,
  A extends any[] = [],
  S extends string = ' '
> = A['length'] extends C ? A : RepeatString<T, C, [...A, T], `${S}${T}`>;

// Test case
type RS0 = RepeatString<"a".0>; / /"
type RS1 = RepeatString<"a".2>; // 'aa'
type RS2 = RepeatString<"ab".3>; // 'ababab'
Copy the code

Similar to question 31, an additional S type is added to return the final result, and A records the number of additions.

Problem 33

Implements a ToNumber utility type for converting numeric string types to numeric types. The specific usage example is as follows:

type ToNumber<T extends string> = // Your implementation code

type T0 = ToNumber<"0">; / / 0
type T1 = ToNumber<"10">; / / 10
type T2 = ToNumber<"20">; / / 20
Copy the code

ToNumber tool type implementation:

type ToNumber<
  T extends string,
  S extends any[] = [],
  L extends number = S['length']
> = `${L}` extends T ? L : ToNumber<T, [...S, ' '>;Copy the code

There is no direct number-crunching in TypeScript, but you can convert array lengths to strings and then match strings that require string conversions.

1. S type is cumulative record, L obtains the array type length of S;

${L} = T; ${L} = T;

Problem 34

Implement a SmallerThan utility type to compare the size of numeric types. The specific usage example is as follows:

type SmallerThan<
  N extends number,
  M extends number,
> = // Your implementation code

// Test case
type S0 = SmallerThan<0.1>; // true
type S1 = SmallerThan<2.0>; // false
type S2 = SmallerThan<8.10>; // true
Copy the code

SmallerThan tool type implementation:

type SmallerThan<
  N extends number,
  M extends number,
  A extends any[] = []
> = A['length'] extends M  Returns false 1 => extends 2? false
  ? false
  : A['length'] extends N // if M = 1 then N should be 0, so M > N => 1 extends 1 true
  ? true
  : SmallerThan<N, M, [...A, ' '>;// otherwise A length + 1

// Test case
type ST1 = SmallerThan<0.0> // false
type ST2 = SmallerThan2<1.2>; // true
Copy the code

By default, an empty array is incremented, which matches first and which is smaller.

For example, passing 0, 0 to N, M:

A[‘length’] extends 0. 0 extends 0 is true. If M is 0, then either M===N or N > M is returned.

For example, passing 1, 2 to N, M:

A[‘length’] = 1; A[‘length’] = 1;

1 extends 1 extends 1 extends 2 extends 1 extends 1 extends 2 extends 1 extends 1 extends 1 M > N

Problem 35

Implement an Add utility type that adds values corresponding to numeric types. The specific usage example is as follows:

type Add<T, R> = // Your implementation code

type A0 = Add<5.5>; / / 10
type A1 = Add<8.20> / / 28
type A2 = Add<10.30>; / / 40
Copy the code

The Add tool type implements:

type GenArr<N extends number, S extends any[] = []> = S['length'] extends N
  ? S
  : GenArr<N, [...S, ' '>;type Add<N extends number, M extends number> = [
  ...GenArr<N>,
  ...GenArr<M>
]['length'];

// Test case
type Add1 = Add<1.2>; / / 3
type Add2 = Add<100.2>; / / 102
Copy the code

After the previous problems, it’s easy to add, just build arrays of numeric lengths.

The GenArr tool type builds the corresponding length array by numeric values.

Problem 36

Implements a Filter utility type for type filtering based on the value of the type variable F. The specific usage example is as follows:

type Filter<T extends any[], F> = // Your implementation code

type F0 = Filter<[6."lolo".7."semlinker".false].number>; / / [6, 7)
type F1 = Filter<["kakuqo".2["ts"]."lolo"].string>; // ["kakuqo", "lolo"]
type F2 = Filter<[0.true.any."abao"].string>; // [any, "abao"]
Copy the code

Filter tool type implementation:

type Filter<T extends any[], F, R extends any[] = []> = T extends [
  infer A,
  ...infer B
]
  ? [A] extends [F]
    ? Filter<B, F, [...R, A]>
    : Filter<B, F, R>
  : R;

// Test case
type F0 = Filter<[6.'lolo'.7.'semlinker'.false].number>; / / [6, 7)
type F1 = Filter<["kakuqo".2["ts"]."lolo"].string>; // ["kakuqo", "lolo"]
type F2 = Filter<[0.true.any."abao"].string>; // [any, "abao"]
type F3 = Filter<[never.number | string.any."abao"].string>; // [never, any, "abao"]
Copy the code

1. R is the result of type filtering based on the value of type variable F;

Infer A = infer B; infer B = infer B; infer B = infer A

3. Converting to tuple extends [F] prevents union types from being distributed in conditional judgments.

Problem 37

Implement a Flat utility type that supports Flat array types. The specific usage example is as follows:

type Flat<T extends any[] > =// Your implementation code

type F0 = Flat<[]> / / []
type F1 = Flat<['a'.'b'.'c'] >// ["a", "b", "c"]
type F2 = Flat<['a'['b'.'c'], ['d'['e'['f']]]] >// ["a", "b", "c", "d", "e", "f"]
Copy the code

Flat tool type implementation:

type Flat<T extends any[]> = T extends [infer First, ...infer Rest]
  ? First extends any[]? [...Flat<First>, ...Flat<Rest>] : [First, ...Flat<Rest>] : [];// Test case
type F1 = Flat<[[1.2.3.4[5]], 6>;/ / [6]
type F2 = Flat<['a'['b'.'c'], ['d'['e'['f']]]] >// ["a", "b", "c", "d", "e", "f"]
Copy the code

Infer First [infer First,…infer Rest

2. If T is a number of items, the First item determines whether it is still an array and flattens the multidimensional array. If so, continue recursively flattening Frist and recursively flattening Rest, otherwise First is not mostly array and recursively flattening Rest.

3. If the array is empty, return [].

Problem 38

Implements the StartsWith utility type that determines whether the string literal type T begins with the given string literal type U and returns a Boolean based on the result. The specific usage example is as follows:

type StartsWith<T extends string, U extends string> = // Your implementation code

type S0 = StartsWith<'123'.'12'> // true
type S1 = StartsWith<'123'.'13'> // false
type S2 = StartsWith<'123'.'1234'> // false
Copy the code

In addition, we continue to implement the EndsWith utility type, determine whether the string literal type T ends in the given string literal type U, and return a Boolean based on the result. The specific usage example is as follows:

type EndsWith<T extends string, U extends string> = // Your implementation code

type E0 = EndsWith<'123'.'23'> // true
type E1 = EndsWith<'123'.'13'> // false
type E2 = EndsWith<'123'.'123'> // true
Copy the code

StartsWith tool type implementation:

type StartsWith<
  T extends string,
  U extends string
> = T extends `${U}${infer Rest}` ? true : false;

// Test case
type S0 = StartsWith<"123"."12">; // true
type S1 = StartsWith<"123"."13">; // false
type S2 = StartsWith<"123"."1234">; // false
Copy the code

Infer Rest ${U}${infer Rest} will start with U and return true if the infer Rest variable type meets the constraint otherwise return false.

The EndsWith tool type implements:

type EndsWith<T extends string, U extends string> = T extends `${infer Head}${U}` ? true : false;


// Test case
type E0 = EndsWith<"123"."23">; // true
type E1 = EndsWith<"123"."13">; // true
type E2 = EndsWith<"123"."123">; // true
Copy the code

${infer Head}${U} With the removal of left space right space topic type logic.

Problem 39

Implement the IsAny utility type to determine whether type T is of any type. The specific usage example is as follows:

type IsAny<T> = // Your implementation code

type I0 = IsAny<never> // false
type I1 = IsAny<unknown> // false
type I2 = IsAny<any> // true
Copy the code

IsAny tool type implementation:

type IsAny<T> = 0 extends 1 & T ? true : false;

type I0 = IsAny<never>; // false
type I1 = IsAny<unknown>; // false
type I2 = IsAny<any>; // true
Copy the code

Crossing any with any type is equal to any implementation

The any type is a black hole that swallows most types except never.

type A0 = any & 1 // any
type A1 = any & boolean // any
type A2 = any & never // never
Copy the code

Therefore, a prefix of 0 extends cross result is required to prevent cases where the cross result is of type never.

40 questions

Implements AnyOf utility types, returning true as long as any element in the array is not of type Falsy, {}, or [], and false otherwise. Return false if the array is empty. The specific usage example is as follows:

type AnyOf<T extends any[] > =// Your implementation code

type A0 = AnyOf<[]>; // false
type A1 = AnyOf<[0.' '.false> {}], [],// false
type A2 = AnyOf<[1."".false> {}], [],// true
Copy the code

AnyOf tool type implementation:

type NotEmptyObject<T> = T extends{}? ({}extends T ? false : true) : true;
type Flasy = 0 | ' ' | false | [];
type AnyOf<T extends any[]> = T extends [infer First, ...infer Rest]
  ? [First] extends [Flasy]
    ? AnyOf<Rest>
    : NotEmptyObject<First>
  : false;

type A0 = AnyOf<[]>; // false
type A1 = AnyOf<[0.' '.false, [], {}]>; // false
type A2 = AnyOf<[1.' '.false, [], {}]>; // true
type A3 = AnyOf<[0.' ' | 2.false, [], {}]>; // trueNon-falsy, {}, or [] typesCopy the code

The NotEmptyObject tool type determines whether it is an empty object {}, and returns false if it is an empty object.

Type = Flasy defines a type that belongs to Falsy;

1. Take out the First item in turn and judge whether it is of Falsy type through tuples (tuples avoid joint type distribution execution). If the current item First meets Falsy type, continue to recursively take out elements in turn for judgment; otherwise, judge whether it is an empty object. If it is not the Falsy type, {} type, or [] type.

Problem 41

Implement the Replace utility type, which implements string type replacement operations. The specific usage example is as follows:

type Replace<
  S extends string,
  From extends string,
  To extends string
> = // Your implementation code
  
type R0 = Replace<' '.' '.' '> / /"
type R1 = Replace<'foobar'.'bar'.'foo'> // "foofoo"
type R2 = Replace<'foobarbar'.'bar'.'foo'> // "foofoobar"
Copy the code

In addition, we continue to implement the ReplaceAll utility type, which is used to ReplaceAll substrings that meet the criteria. The specific usage example is as follows:

type ReplaceAll<
  S extends string,
  From extends string,
  To extends string
> = // Your implementation code

type R0 = ReplaceAll<' '.' '.' '> / /"
type R1 = ReplaceAll<'barfoo'.'bar'.'foo'> // "foofoo"
type R2 = ReplaceAll<'foobarbar'.'bar'.'foo'> // "foofoofoo"
type R3 = ReplaceAll<'foobarfoobar'.'ob'.'b'> // "fobarfobar"
Copy the code

Replace tool type implementation:

type Replace<
  S extends string,
  From extends string,
  To extends string
> = S extends `${infer H}${From}${infer R}` ? `${H}${To}${R}` : S;

// Test case
type R0 = Replace<' '.' '.' '>; / /"
type R1 = Replace<'foobar'.'bar'.'foo'>; // "foofoo"
type R2 = Replace<'foobarbar'.'bar'.'foo'>; // "foofoobar"
Copy the code

Infer 1. Using extends and infer in conjunction with the writing of string template variables, a specific substring can be extracted and the result can be returned by changing From To.

ReplaceAll tool type implementation:

type ReplaceAll<
  S extends string,
  From extends string,
  To extends string
> = S extends `${infer H}${From}${infer R}`
  ? `${ReplaceAll<H, From, To>}${To}${Replace<R, From, To>}`
  : S;

// Test case
type R0 = ReplaceAll<' '.' '.' '> / /"
type R1 = ReplaceAll<'barfoo'.'bar'.'foo'> // "foofoo"
type R2 = ReplaceAll<'foobarbar'.'bar'.'foo'> // "foofoofoo"
type R3 = ReplaceAll<'foobarfoobar'.'ob'.'b'> // "fobarfobar"
Copy the code

The ReplaceAll utility type retrieves substrings using recursion.

Problem 42

Implements the IndexOf utility type, which is used to get the index value of the specified item in an array type. If none exists, the -1 literal type is returned. The specific usage example is as follows:

type IndexOf<A extends any[], Item> = // Your implementation code

type Arr = [1.2.3.4.5]
type I0 = IndexOf<Arr, 0> // -1
type I1 = IndexOf<Arr, 1> / / 0
type I2 = IndexOf<Arr, 3> / / 2
Copy the code

IndexOf tool types implement:

type IndexOf<A extends any[], Item, L extends any[] = []> = A extends [
  infer F,
  ...infer R
]
  ? F extends Item
    ? L['length']
    : IndexOf<R, Item, [...L, 1] > : - (in Chinese)1;

  type Arr = [1.2.3.4.5]
  type I0 = IndexOf<Arr, 0> // -1
  type I1 = IndexOf<Arr, 1> / / 0
  type I2 = IndexOf<Arr, 3> / / 2
Copy the code

Build an array to keep track of which items are iterated over, so that when a match is made it returns the length, which is the index value.

1. Check whether the first Item of the array is equal to the value specified by Item.

2. If not, continue to increase the length of L array;

Infer F = infer R; infer F = infer R;

Problem 43

Implements a Permutation tool type that, when an association type is entered, returns an array of all Permutation types containing the association type. The specific usage example is as follows:

type Permutation<T, K=T> = // Your implementation code

// ["a", "b"] | ["b", "a"]
type P0 = Permutation<'a' | 'b'>  // ['a', 'b'] | ['b' | 'a']
// type P1 = ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] 
// | ["b", "c", "a"] | ["c", "a", "b"] | ["c", "b", "a"]
type P1 = Permutation<'a' | 'b' | 'c'> 
Copy the code

The Permutation tool type implements:

type Permutation<T, K = T> = [T] extends [never]? [] : Kextends K
  ? [K, ...Permutation<Exclude<T, K>>]
  : never;

type P0 = Permutation<'a' | 'b'>; // ['a', 'b'] | ['b' | 'a']
type P1 = Permutation<'a' | 'b' | 'c'>; 
// => ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] 
// |["c", "a", "b"] | ["c", "b", "a"]
Copy the code

Directly introduced with ‘a’ | ‘b’ | ‘c’ for example:

This simplifies the result of excluding1And ['a'. Permutation<'b' | 'c'>] | ['b'. Permutation<'a' | 'c'>] | 
['c'. Permutation<'a' | 'b'>]
 
2, = >... Permutation<'b' | 'c'> Recursively do redistribute => ['b'. Permutation<'c'>] | ['c'. Permutation<'b'>] = > ['b'.'c'"|"'c'.'b']

3, and again1A combination is (... Will expand the result) => ['a'.'b'.'c'"|"'a'.'c'.'b'Repeat the above1 2 3Step to get final result =>type P1 = ["a"."b"."c""|""a"."c"."b""|""b"."a"."c""|""b"."c"."a""|""c"."a"."b""|""c"."b"."a"]                   
Copy the code

Problem 44

Implements the Unpacked tool type for “unpacking” the type. The specific usage example is as follows:

type Unpacked<T> = // Your implementation code

type T00 = Unpacked<string>;  // string
type T01 = Unpacked<string[] >;// string
type T02 = Unpacked<() = > string>;  // string
type T03 = Unpacked<Promise<string> >;// string
type T04 = Unpacked<Unpacked<Promise<string> [] > >;// string
type T05 = Unpacked<any>;  // any
type T06 = Unpacked<never>;  // never
Copy the code

Unpacked tool type implementation:

type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends(... args:any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T;

// Test case
type T00 = Unpacked<string>;  // string
type T01 = Unpacked<string[] >;// string
type T02 = Unpacked<() = > string>;  // string
type T03 = Unpacked<Promise<string> >;// string
type T04 = Unpacked<Unpacked<Promise<string> [] > >;// string
type T05 = Unpacked<any>;  // any
type T06 = Unpacked<never>;  // never
Copy the code

1. (Infer U)[] processes array types and returns their concrete types;

2. (… Args: any[]) => infer U processes function types and infer the return types of the functions;

3. Promise

processes Promise types, which return nested calls;

4, otherwise not one of the above three types, directly return its own type.

Problem 45

Implements the JsonifiedObject utility type, used to serialize the Object object type. The specific usage example is as follows:

type JsonifiedObject<T extends object> = // Your implementation code

type MyObject = {
  str: "literalstring".fn: () = > void.date: Date.customClass: MyClass,
  obj: {
    prop: "property".clz: MyClass,
    nested: { attr: Date}}},declare class MyClass {
  toJSON(): "MyClass";
}

/** * type JsonifiedMyObject = { * str: "literalstring"; * fn: never; * date: string; * customClass: "MyClass"; * obj: JsonifiedObject<{ * prop: "property"; * clz: MyClass; * nested: { * attr: Date; *}; *} >; *} * /
type JsonifiedMyObject = Jsonified<MyObject>;
declare let ex: JsonifiedMyObject;
const z1: "MyClass" = ex.customClass;
const z2: string = ex.obj.nested.attr;
Copy the code

JsonifiedObject implements the tool type:

type JsonifiedObject<T extends object> = {
  [K in keyof T]: T[K] extends { toJSON(): infer Return }
    ? ReturnType<T[K]['toJSON']>
    : T[K] extends(... args:any[]) = >any
    ? never
    : T[K] extends object
    ? JsonifiedObject<T[K]>
    : T[K];
};

declare class MyClass {
  toJSON(): 'MyClass';
}

type MyObject = {
  str: 'literalstring';
  fn: () = > void;
  date: Date;
  customClass: MyClass;
  obj: {
    prop: 'property';
    clz: MyClass;
    nested: { attr: Date };
  };
};


// Test case
/** * type JsonifiedMyObject = { * str: "literalstring"; * fn: never; * date: string; * customClass: "MyClass"; * obj: JsonifiedObject<{ * prop: "property"; * clz: MyClass; * nested: { * attr: Date; *}; *} >; *} * /
type JsonifiedMyObject = JsonifiedObject<MyObject>;
declare let ex: JsonifiedMyObject;
const z1: 'MyClass' = ex.customClass;
const z2: string = ex.obj.nested.attr;
Copy the code

Walk through the object in turn, doing special treatment for some attribute types

1, the property defined as the MyClass class needs to take the value of the toJSON function property, as the property literal;

Properties defined as function types need to be changed to never;

3. Deep objects need recursive traversal.

Problem 46

Implement the RequireAllOrNone tool type for the following functions. That is, when the age attribute is set, the gender attribute becomes mandatory. The specific usage example is as follows:

interface Person {
  name: string; age? :number; gender? :number;
}

type RequireAllOrNone<T, K extends keyof T> = // Your implementation code

const p1: RequireAllOrNone<Person, 'age' | 'gender'> = {
  name: "lolo"
};

const p2: RequireAllOrNone<Person, 'age' | 'gender'> = {
  name: "lolo".age: 7.gender: 1
};
Copy the code

RequireAllOrNone implements the tool type:

type RequireAllOrNone<T, K extends keyof T> = Omit<T, K> &
  (Required<Pick<T, K>> | Partial<Record<K, never> >);// Test case
const p1: RequireAllOrNone<Person, 'age' | 'gender'> = {
  name: "lolo"
};

const p2: RequireAllOrNone<Person, 'age' | 'gender'> = {
  name: "lolo".age: 7.gender: 1
};

// Error: Gender attribute missing
const p3: RequireAllOrNone<Person, 'age' | 'gender'> = {
  name: 'lolo'.age: 1};Copy the code

If the object has an age attribute, then it needs a gender attribute.

1. Results of reverse selection excluding K;

2, Required > select the attributes that K has in T and make them mandatory;

Partial

>) Partial

>) Partial

>


More than 4, the ‘age’ | ‘gender’, for example:

{ age: number ; gender: number} | { age? : undefined | never; gender? : undefined | never}Copy the code

So if the object has an age attribute, or a gender attribute then it matches the former object type, otherwise it matches the latter object type;

5. The name attribute needs to be retained, so use reverse selection to separate the name attribute.

Problem 47

Implement the RequireExactlyOne tool type for the following functions. That is, only age or gender attributes can be included, not both. The specific usage example is as follows:

interface Person {
  name: string; age? :number; gender? :number;
}

// Can contain only one Key in Keys
type RequireExactlyOne<T, Keys extends keyof T> = // Your implementation code

const p1: RequireExactlyOne<Person, 'age' | 'gender'> = {
  name: "lolo".age: 7};const p2: RequireExactlyOne<Person, 'age' | 'gender'> = {
  name: "lolo".gender: 1
};

// Error
const p3: RequireExactlyOne<Person, 'age' | 'gender'> = {
  name: "lolo".age: 7.gender: 1
};
Copy the code

RequireExactlyOne tool type implementation:

// // wants to build like this to meet the conditions
type Test = { name: string } & ({ age: number, gender? :never} | { age? :never.gender: number })

type RequireExactlyOne<T, Keys extends keyof T, K extends keyof T = Keys> = Keys extends any
  ? Omit<T, K> & Required<Pick<T, Keys>> & Partial<Record<Exclude<K, Keys>, never> > :never;

type TTT =
  | ({ name: string} and {age: number} & { gender? :never({}) |name: string} & { age? :never} and {gender: number });
Copy the code

Distributed execution is implemented using the union type extends, and then it’s a matter of making only one of the union type rules take effect, so the other properties need to be set to the optional Never.

With the incoming ‘age’ | ‘gender’, for example:

Keys is used for distributed execution. Keys extends any is used to trigger the union type to distribute execution in a condition. K preserves the union type.

The following will be executed

{ name: string} and {age: number} & { gender? :never } |
{ name: string} & { age? :never} and {gender: number }
Copy the code

And if we simplify this, we’ll get the case where we satisfy the Test above.

Question 48

ConsistsOnlyOf implements the ConsistsOnlyOf utility type to determine whether the LongString string type consists of zero or more subStrings. The specific usage example is as follows:

type ConsistsOnlyOf<LongString extends string, Substring extends string> = // Your implementation code

type C0 = ConsistsOnlyOf<'aaa'.'a'> //=> true
type C1 = ConsistsOnlyOf<'ababab'.'ab'> //=> true
type C2 = ConsistsOnlyOf<'aBa'.'a'> //=> false
type C3 = ConsistsOnlyOf<' '.'a'> //=> true
Copy the code

ConsistsOnlyOf tool type implementation:

type ConsistsOnlyOf<
  LongString extends string,
  Substring extends string
> = LongString extends ' '
  ? true
  : LongString extends `${Substring}${infer B}`
  ? ConsistsOnlyOf<B, Substring>
  : false;
Copy the code

1. Check whether it is an empty string. If it is, return true.

2. Otherwise, use template string syntax and infer to match and reduce ideas from the beginning. If the match is successful, the recursion continues until the string is empty.

At the end of

New titles will be updated in this article, and the corresponding TypeScipt tool type library recommends Type-fest.