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
- use
Parameters
+ReturnType
Tool 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.
- use
infer
way
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
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.