ts
reference
- https://github.com/microsoft/TypeScript
- https://ts.xcatliu.com/
- https://jkchao.github.io/typescript-book-chinese/#why
- http://www.patrickzhong.com/TypeScript/PREFACE.html
- https://github.com/type-challenges/type-challenges
- https://zhuanlan.zhihu.com/p/614782362
- type and interface: https://zhuanlan.zhihu.com/p/561423056
- as unknown as: https://stackoverflow.com/questions/69399211/typescript-why-does-as-unknown-as-x-work
as const: link1; link2;- ts define recursive type: https://stackoverflow.com/questions/47842266/recursive-types-in-typescript;
type-challenges
easy
pick
Implement the built-in Pick<T, K> generic without using it.
- example:
js
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}- solutions:
ts
export type MyPick<T,K extends keyof T> = {
[P in K]: T[P]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
console.log("easy:MyPick", todo)tupleToObject
Give an array, transform into an object type and the key/value must in the given array.
- example:
ts
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}- solutions:
ts
export type TupleToObject<T extends ReadonlyArray<number | string>> = {
[K in T[number]]: K;
};
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;
type result = TupleToObject<typeof tuple>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
let value: result = {
tesla: 'tesla',
'model 3': 'model 3',
'model X': 'model X',
'model Y': 'model Y',
};
console.log('easy', 'tupleToObject', value);readonly
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.- example:
ts
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property- solutions:
ts
export type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
// todo.title = "Hello" // Error: cannot reassign a readonly property
// todo.description = "barFoo" // Error: cannot reassign a readonly property
console.log("easy: readonly", todo)Length of Tuple
For given a tuple, you need create a generic Length, pick the length of the tuple- example:
ts
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5- solutions:
ts
export type MyTupleLength<T extends readonly any[]> = T['length']
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type TeslaLength = MyTupleLength<tesla> // expected 4
type SpaceXLength = MyTupleLength<spaceX> // expected 5
const tLen: TeslaLength = 4;
const sLen: SpaceXLength = 5;
console.log("easy: myTupleLength", tLen, sLen)Exclude
Implement the built-in Exclude<T, U>
Exclude from T those types that are assignable to U- example:
ts
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'- solutions:
ts
export type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
const value: Result = 'b'
console.log("medium:exclude",value)Awaited
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?- example:
ts
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string- solutions:
ts
export type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? R extends PromiseLike<any> ? MyAwaited<R> : R : never
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
let value: Result = '1';
console.log("easy: awaited", value)If
Implement the util type If<C, T, F> which accepts condition C, a truthy value T, and a falsy value F. C is expected to be either true or false while T and F can be any type.- example:
ts
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'- solutions:
ts
export type If<C extends Boolean, A, B> = C extends true? A : B
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
const aValue: A = 'a';
console.log("easy-8: If", aValue)Concat
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- example:
ts
type Result = Concat<[1], [2]> // expected to be [1, 2]- solutions:
ts
export type Concat<T extends any[], K extends any[]> = [...T , ...K]
type Result = Concat<[1], [2]> // expected to be [1, 2]
const value: Result = [1, 2]
console.log('easy: concat', value)Includes
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.- example:
ts
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`- solutions:
ts
// export type Includes1<T extends any[], V> = V extends T[number] ? true : false
export type Includes<T extends any[], V> = V extends T[number]? true : false
// type Exact<A, B> = (<G>() => G extends A ? 1 : 2) extends
// (<G>() => G extends B ? 1 : 2) ? true : false;
// type Includes<T extends readonly any[], U> = T extends [infer Head, ...infer Tail] ? Exact<Head,U> extends true ? true : Includes<Tail, U> : false
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
const value: isPillarMen = false
console.log('easy: includes', value)Push
txt
Implement the generic version of Array.push- example:
ts
type Result = Push<[1, 2], '3'> // [1, 2, '3']- solutions:
ts
export type Push<T extends unknown[], U> = [...T, U]
type Result = Push<[1, 2], '3'> // [1, 2, '3']
const value: Result = [1, 2, '3']
console.log('easy-11: push', value)medium
Get Return Type
Implement the built-in ReturnType<T> generic without using it.
- example
ts
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // should be "1 | 2"- solutions:
ts
export type MyReturnType<T extends (...args: any[]) => unknown> = T extends (...args: any[]) => infer U ? U : neverOmit
Implement the built-in Omit<T, K> generic without using it.
Constructs a type by picking all properties from T and then removing K- example:
ts
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}- solutions:
ts
export type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
interface Todo {
title: string
description: string
completed: string
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todoValue: TodoPreview = {
completed: "hi, my omit type!"
}
console.log("medium:", "MyOmit", todoValue)Readonly2
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>.- example:
ts
interface Todo {
title: string
description: string
completed: boolean
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: "Hey",
description: "foobar",
completed: false,
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK- solutions:
ts
export type MyReadonly2<T, P extends keyof T> = {
readonly [K in P]: T[K]
} & {
[K in Exclude<keyof T, P>]: T[K]
}
interface Todo {
title: string
description: string
completed: boolean
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: "Hey",
description: "foobar",
completed: false,
}
// todo.title = "Hello" // Error: cannot reassign a readonly property
// todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK
console.log('medium: MyReadonly2', todo)Deep Readonly
Implement a generic DeepReadonly<T> which make 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 be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.- example:
ts
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`- solutions:
ts
export type DeepReadonly<T extends { [propName: string]: any }> = {
readonly [key in keyof T]: T[key] extends { [propName: string]: any } ? DeepReadonly<T[key]> : T[key]
}
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`
const todoValue: Todo = {
x: {
a: 1,
b: 'hi',
},
y: 'hey',
}
// todoValue.x = 3;
console.log('medium: deep readonly', todoValue)Tuple to Union
Implement a generic TupleToUnion<T> which covers the values of a tuple to its values union.- example:
ts
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'- solutions:
ts
export type TupleToUnion<T extends any[]> = T extends [infer i,...infer test] ? i | TupleToUnion<test> : never
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
const testValue: Test = '1';
console.log("medium: tupleToUnion", testValue);Chainable Options
Chainable options are commonly used in Javascript. But when we switch to TypeScript, can you properly 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.- example:
ts
declare const config: Chainable
const 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: number
name: string
bar: {
value: string
}
}- solutions:
ts
export type Chainable<Options = {}> = {
option<K extends string, V>(
key: K,
value: V
): Chainable<Options & { [S in K]: V }>;
get(): { [P in keyof Options]: Options[P] };
};
// export type Chainable<Options= {}> = {
// option<K extends string, V>(
// key: K,
// value: V
// ): Chainable<Options & { [S in K]: V } >,
// get(): {
// [P in keyof Options]: Options[P]
// },
// }
const config: Chainable = {
option: function (){
return this
},
get: function (){
return {}
}
} as unknown as Chainable
const 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: number
// name: string
// bar: {
// value: string
// }
// }
console.log("medium: Chainable", result)