Implement the built-in Pick<T, K>
generic without using it.
Constructs a type by picking the set of properties K
from T
For example
interface Todo {title: stringdescription: stringcompleted: boolean}type TodoPreview = MyPick<Todo, 'title' | 'completed'>const todo: TodoPreview = {title: 'Clean room',completed: false}
answer
type MyPick<T, U extends keyof T> = {[K in U]: T[K]}
不知道的知识点
extends
TypeScript 2.8 引入了条件关键字:extends
T extends U ? X : Y
如果
T
包含的类型是U
包含类型的子集,那么取结果X
,否则取结果Y
keyof
TypeScript允许我们遍历某种类型属性,并通过keyof
操作符提取其属性的名称。keyof
操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Person {name: string;age: number;location: string;}type K1 = keyof Person; // "name" | "age" | "location"type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...type K3 = keyof { [x: string]: Person }; // string | number
Implement the built-in Readonly<T>
generic without using it.
Constructs a type with all properties of T set to readonly, meaning the properties of the constructed type cannot be reassigned.
For example
interface Todo {title: stringdescription: string}const todo: MyReadonly<Todo> = {title: "Hey",description: "foobar"}todo.title = "Hello" // Error: cannot reassign a readonly propertytodo.description = "barFoo" // Error: cannot reassign a readonly property
answer
type MyReadonly<T> = {readonly [key in keyof T]: T[key]}
Give an array, transform into an object type and the key/value must in the given array
For example
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as consttype result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
answer
type TupleToObject<T extends readonly Array<string | number | symbol>> = {[P in T[number]]: P;}
Implement a generic First<T>
that takes An Array T
and return it's first element's type
For example
type arr1 = ['a', 'b', 'c'];type arr2 = [3, 2, 1];type head1 = First<arr1>
answer
type First<T extends readonly unknown[]> =T extends [infer ElementType, ...infer Tail]? Element: never
infer
可以在extends
的条件语句中推断待推断的类型。
infer 解包
// 获取数组元素的类型,在不会 infer 之前是这样做的type Ids = number[];type Names = string[];type Unpacked<T> = T extends Names ? String : T extends Ids ? number : T;type idType = Unpacked<Ids>; // idType 类型为 numbertype nameType = Unpacked<Names>; // nameType 类型为 string
type Unpacked<T> = T extends (infer R)[] ? R : T;type idType = Unpacked<Ids>; // idType 类型为 number;type nameType = Unpacked<Names>; // nameType 类型为 string;
推断联合类型
type Foo<T> = T extends {a: infer U; b: infer U} ? U : never;type T10 = Foo<{ a: string; b: string }>; // T10 类型为 string.type T11 = Foo<{ a: string; b: number }>; // T11 类型为 string | number.
For given a tuple, you need to create a generic Length
, pick the length of the tuple
For example
type tesla = ['tesla', 'model 3', 'model X', 'model Y']type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']type teslaLength = Length<tesla> // expected 4type spaceXLength = Length<spaceX> // expected 5
answer
// answer1type Length<T extends readonly any[]> = T["length"]// answer2type Length<T> = T extends any[] ? T['length'] : never;// answer3type Length = T extends {length: infer Length} ? Length : never;
Implement the built-in Exclude<T, U>
Exclude from T those types that are assignable to U
answer
type MyExclude<T, U> = T extends U ? never : T;
If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type? For example if we have Promise<ExampleType>
how to get ExampleType
?
This question is ported from the original article by @maciejsikora
answer
// answer1type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;// answer2type MyAwaited<T extends Promise<any>> = T extends Promise<infer R> ? (R extends Promise<any> ? MyAwaited<R> : R) : never;
Implement utils If
which accepts condition C
, a truthy return type T
, and a falsy return type F
. C
is expected to be either true
or false
while T
and F
can be any type.
For example:
type A = If<true, 'a', 'b'> // expected to be 'a'type B = If<false, 'a', 'b'> // expected to be 'b'
answer
type If<C extends boolean, T, F> = C extends true ? T : F;
Implement the JavaScript Array.concat
function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr order
For example
type Result = Concat<[1], [2]> // expected to be [1, 2]
answer
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
Implement the JavaScript Array.includes
function in the type system. A type takes the two arguments. The output should be a boolean true
or false
.
For example
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
answer
type Includes<T extends readonly any[], U> = T extends [infer X, ...infer Y] ? Equal<X, U> extends true ? true : Includes<Y, U> : false;
Implement the generic version of Array.push
For example
type Result = Push<[1, 2], '3'> // [1, 2, '3']
answer
type Push<T extends unknown[], U> = [...T, U];
Implement the type version of Array.unshift
For example
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
answer
type Unshift<T extends unknown[], U> = [U, ...T]
Implement the built-in Parameters generic without using it.
answer
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer X) => any ? X : never;type MyParameters<T> = T extends (...args: infer P ) => any ? P : T
Implement the built-in ReturnType<T>
generic without using it.
For example
const fn = (v: boolean) => {if (v) {return 1} else {return 2}}type a = MyReturnType<typeof fn> // should "1" | "2"
answer
// answer1type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;// answer2type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never;
Implement the built-in Omit<T, K>
generic without using it.
Constructs a type by picking all properties from T
and then removing K
For example
interface Todo {title: stringdescription: stringcompleted: boolean}type TodoPreview = MyOmit<Todo, 'description' | 'title'>const todo: TodoPreview = {completed: false}
answer
type MyOmit<T, K extends keyof T> = {[P in keyof T as P extends K ? never : P ] : T[P]}
Implement a generic MyReadonly2<T, K>
which takes two type argument T
and K
K
specify the set of properties of T
that should set to Readonly. When K
is not provided, it should make all properties readonly just like the normal Readonly<T>
.
For example
interface Todo {title: stringdescription: stringcompleted: boolean}const todo: MyReadonly2<Todo, 'title' | 'description'> = {title: "Hey",description: "foobar",completed: false,}todo.title = "Hello" // Error: cannot reassign a readonly propertytodo.description = "barFoo" // Error: cannot reassign a readonly propertytodo.completed = true // OK
answer
type MyReadonly2<T, K extends keyof T = keyof T> ={ readonly [key in K]: T[key] }& { [key in keyof T as (key extends k ? never : key)] : T[key] }
// Todo need to understand
Implement a generic DeepReadonly<T>
which makes every parameter of an object - and its sub-objects recursively - readonly.
You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes, and so on do not need to take into consideration. However, you can still challenge yourself by covering different cases as many as possible.
For example.
type X = {x: {a: 1,b: 'hi'},y: 'hey'}type Expected = {readonly x: {readonly a: 1readonly b: 'hi'}readonly y: 'hey'}type Todo = DeepReadonly<X> // should be same as `Expected`
answer
// answer1type DeepReadonly<T extends Record<any, any>> = {readonly [key in keyof T]: T[key] extends string | number | symbol | Function | boolean? T[key]: DeepReadonly<T[key]>}// answer2type DeepReadonly<T> = {readonly [key in keyof T] : T[key] extends Function ? T[key] : T[key] extends object ? DeepReadonly<T[key]> : T[key];}
Implement a generic TupleToUnion<T>
which covers the values of a tuple to its values union
For example
type Arr = ['1', '2', '3']type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
answer
// answer1type TupleToUnion<T extends readonly unknown[]> = T extends [infer First,...infer Rest]? First | TupleToUnion<Rest>: never// answer2type TupleToUnion<T extends unknown[]> = T[number]
Chainable options are commonly used in JavaScript. But when we switch to TypeScript, can you property type it?
In this challenge, you need to type an object or a class - whatever you like - to provide two function option(key, value)
and get()
. In option
, you can extend the current config type by the given key and value. We should about to access the final result via get
.
For example
declare const config: Chainableconst result = config.option('foo', 123).option('name', 'type-challenges').option('bar', { value: 'Hello World' }).get()// expect the type of result to be:interface Result {foo: numbername: stringbar: {value: string}}
You don't need to write any js/ts logic to handle the problem - just in type level.
You can assume that key
only accepts string
and the value
can be anything - just leave it as-is. Same key
won't be passed twice.
answer
type chainable<T extends Record<string, unknown> = {}> = {option<K extends string, V = unknown>(key: K, value: V): Chainable<T & Record<K, V>>get(): T}
TypeScript 4.0 is recommended in this challenge
Implement a generic Last<T>
that takes an Array T
and returns its last element.
For example
type arr1 = ['a', 'b', 'c']type arr2 = [3, 2, 1]type tail1 = Last<arr1> // expected to be 'c'type tail2 = Last<arr2> // expected to be 1
answer
// answer1type Last<T extends any[]> = T extends [infer L, ...infer R] ? (R[0] extends undefined ? L : Last<R>) : never;// answer2type LastOfArray<T extends readonly any[]> = T extends [...any[], infer R] ? R : never;
TypeScript 4.0 is recommended in this challenge
Implement a generic Pop<T>
that takes an Array T
and returns an Array without it's last element.
For example
type arr1 = ['a', 'b', 'c', 'd']type arr2 = [3, 2, 1]type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']type re2 = Pop<arr2> // expected to be [3, 2]
Extra: Similarly, can you implement Shift
, Push,
and Unshift
as well?
answer
type Pop<T extends any[]> = T extends [...infer R, any] ? R : never;
Type the function PromiseAll
that accepts an array of PromiseLike objects, the returning value should be Promise<T>
where T
is the resolved result array.
const promise1 = Promise.resolve(3);const promise2 = 42;const promise3 = new Promise<string>((resolve, reject) => {setTimeout(resolve, 100, 'foo');});// expected to be `Promise<[number, 42, string]>`const p = Promise.all([promise1, promise2, promise3] as const)
answer
// answer1declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{[K in keyof T]: Awaited<T[K]>}>// answer2type PromiseType<T> = T extends Promise<infer PT> ? PT: Ttype PromisesType<T extends readonly any[]> = Promise<T extends readonly [infer First, ...infer Rest]? [PromiseType<First>, ...PromisesType<Rest>]: []>declare const PromiseAll: <T extends readonly any[]>(promises: T) => PromisesType<T>
Sometimes, you may want to lookup for a type in a union to by their attributes.
In this challenge, we would like to get the corresponding type by searching for the common type
field in the union Cat | Dog
. In other words, we will expect to get Dog
for LookUp<Dog | Cat, 'dog'>
and Cat
for LookUp<Dog | Cat, 'cat'>
in the following example.
interface Cat {type: 'cat'breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'}interface Dog {type: 'dog'breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'color: 'brown' | 'white' | 'black'}type MyDogType = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
answer
type LookUp<U, T extends string> = U extends Record<string, any> ? U['type'] extends T ? U : number : never
Implement TrimLeft<T>
which takes an exact string type and returns a new string with the whitespace beginning removed.
For example
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
answer
type Space = ' ' | '\n' | '\t';type TrimLeft<S extends string> = S extends `${Space}${infer R}` ? TrimLeft<R> : S
Implement Trim<T>
which takes an exact string type and returns a new string with the whitespace from both ends removed.
For example
type trimmed = Trim<' Hello World '> // expected to be 'Hello World'
answer