easy
1. 实现pick
实现 TS 内置的 Pick<T, K>,但不可以使用它。
从类型 T 中选择出属性 K,构造成一个新的类型。
例如
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
题解
type MyPick<T, U extends keyof T> = { [K in U]: T[K] }
相关知识
keyof 用于获取某种类型的所有键,其返回类型是联合类型。
2. 实现 Readonly
不要使用内置的Readonly<T>,自己实现一个。
该 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。
也就是不可以再对该对象的属性赋值。
例如:
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
题解
type MyReadOnly<T> = { readonly [K in keyof T] : T[K] }
3. 元组转换为对象
传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
例如:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
题解
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[K in T[number]]: K
}
相关知识
as const相关知识:
4. 第一个元素
实现一个通用First<T>,它接受一个数组T并返回它的第一个元素的类型。
例如:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
题解
因为存在空数组的情况,所以我们需要考虑never[]
type First<T extends any[]> = T extends never[] ? never : T[0]
相关知识
never 类型表示的是那些永不存在的值的类型。
5. 获取元组长度
创建一个通用的Length,接受一个readonly的数组,返回这个数组的长度。
例如:
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
题解
type Length<T extends readonly any[]> = T['length']
6. Exclude
实现内置的Exclude <T,U>
从T中排除可分配给U的那些类型
如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉
示例
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
题解
如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉。
待证实:如果T是联合类型,比如"a" | "b" | "c",运行时为 "a" extends U, "b" extends U, "c" extends U,
type MyExclude<T, U> = T extends U ? never: T
7. Awaited
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
比如:Promise<ExampleType>,请你返回 ExampleType 类型。
测试用例
import { Equal, Expect } from '@type-challenges/utils'
type X = Promise<string>
type Y = Promise<{ field: number }>
type cases = [
Expect<Equal<Awaited<X>, string>>,
Expect<Equal<Awaited<Y>, { field: number }>>,
]
// @ts-expect-error
type error = Awaited<number>
题解
type Awaited<T extends Promise<any>> = T extends Promise<infer P> ? P: never;
相关知识
infer 声明一个类型变量并且对它进行使用
8. If
实现一个 If,接受布尔类型 C,如果为 true 返回类型 T,false 返回类型的 F。同时T、F可以是任何类型。
例如
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
题解
type If<C extends boolean, T, F> = C extends true ? T : F;
9. Concat
实现 JavaScript Array.concat 函数。 接受两个参数,输出应该是一个新数组,按照输入顺序排序。
示例
type Result = Concat<[1], [2]> // expected to be [1, 2]
题解
type Concat<T extends any[], U extends any[]> = [...T, ...U]
10. Includes
实现 JavaScript Array.includes函数。接受两个参数,输出应该是一个布尔值true或false
示例
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
题解
// 乞丐版
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
// 完整版
type IsEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
type Includes<T extends readonly any[], U> = T extends [infer P, ...infer Rest] ? (IsEqual<P, U> extends true ? true : Includes<Rest, U>) : false
11. push
实现 Array.push
示例
type Result = Push<[1, 2], '3'> // [1, 2, '3']
题解
type Push<T extends any[], U> = [...T, U]
12. Unshift
实现 Array.unshift
示例
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
题解
type Unshift<T extends any[], U> = [U, ...T]
13. Parameters
获取函数入参
测试用例
import { Equal, Expect, ExpectFalse, NotEqual } from '@type-challenges/utils'
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: {a: 'A'}): void => {}
const baz = (): void => {}
type cases = [
Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
Expect<Equal<MyParameters<typeof bar>, [boolean, {a: 'A'}]>>,
Expect<Equal<MyParameters<typeof baz>, []>>,
]
题解
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never
medium
1. 获取函数返回类型
不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 范型。
例如:
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
题解
type MyReturnType<T> = T extends (...args: any) => infer P ? P : never
2. 实现 Omit
不使用 Omit 实现 TypeScript 的 Omit<T, K> 范型。
Omit 会创建一个省略 K 中字段的 T 对象。
例如:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
题解
// 1
type MyOmit<T extends object, U extends keyof T> = {
[K in keyof T as (K extends U ? never : K)
]: T[K]
}
// 2
type MyExclude<T, U> = T extends U ? never : T;
type MyOmit<T, U extends keyof T> = { [K in MyExclude<keyof T, U>]: T[K] };
// 3
// 使用Pick与Exclude 这两个 Utility Types
type MyOmit<T, K> = Pick<T,Exclude<keyof T,K>>
3. Readonly 2
实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。
K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。
例如
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
const todo2: MyReadonly2<Todo> = {
title: "Hey",
description: "foobar",
completed: false,
}
todo2.title = "Hello" // Error: cannot reassign a readonly property
todo2.description = "barFoo" // Error: cannot reassign a readonly property
todo2.completed = true // Error: cannot reassign a readonly property
题解
// = keyof T代表如果第二个参数不传,默认为keyof T
type MyReadonly2<T, K extends keyof T = keyof T> = T & {
readonly [P in K] : T[P]
}
4. 深度 Readonly
实现一个通用的DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
const todo: DeepReadonly<X> // should be same as `Expected`
题解
// TODO
type DeepReadonly<T> = T extends { [propName: string]: infer R }
? { readonly [i in keyof T]: DeepReadonly<T[i]> }
: T;
5. 元组转合集
实现泛型TupleToUnion<T>,它覆盖元组的值与其值联合。
例如
type Arr = ['1', '2', '3']
const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'
题解
// TODO
type TupleToUnion<T extends any[]> = T[number];
type TupleToUnion<T> = T extends any[] ? T[number] : never;
6. 最后一个元素
实现一个通用Last<T>,它接受一个数组T并返回其最后一个元素的类型。
例如
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
题解
type Last<T extends any[]> = T extends [...any, infer L] ? L : never;
7. 出堆
实现一个通用Pop<T>,它接受一个数组T并返回一个没有最后一个元素的数组。
例如
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]
额外:同样,您也可以实现Shift,Push和Unshift吗?
题解
type Pop<T extends any[]> = T extends [...infer R, any] ? R : never;
8. Promise.all
键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise<T>,其中T是解析的结果数组。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)
题解
// TODO
declare function PromiseAll<T extends any[]>(
values: readonly [...T]
): Promise<{
[key in keyof T]: T[key] extends PromiseLike<infer V> ? V : T[key];
}>;
9. Type Lookup
有时,您可能希望根据其属性在并集中查找类型。
在此挑战中,我们想通过在联合Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp<Dog | Cat, 'dog'>获得Dog,LookUp<Dog | Cat, 'cat'>获得Cat。
interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
题解
type LookUp<T extends { type: any }, U extends T["type"]> = T extends {
type: U;
}
? U
: never;
type LookUp<U, T> = U extends {type: any} ? (U['type'] extends T ? U : never) : never;
type LookUp<U, T> = U extends {type:infer R} ? R extends T ? U : never : never;