1. 实现TS内置的ReturnType
// TS相关 实现TS内置的 ReturnType<T>
// 不能直接用原生TS的 ReturnType
const func = (a: number, b: number) => {
if (a + b > 10)
return a + b
else
return 'beyond_ten'
}
type a = MyReturnType<typeof func > // 推导出 number | 'beyond_ten'
答案
const func = (a: number, b: number) => {
if (a + b > 10)
return a + b
else
return 'beyond_ten'
}
type MyReturnType<T> = T extends (...args) => infer Value ? Value : never
type a = MyReturnType<typeof func> // 推导出 number | 'beyond_ten'
2. 实现TS内置的 Pick<T, K>
用于从现有的类型中选择一个或多个属性来创建一个新的类型
// TS相关 实现TS内置的 Pick<T, K>
// 不能直接用原生TS的 Pick
interface UserInfo {
name: string;
password: string;
phone: string;
}
type AdminUserInfo = MyPick<UserInfo, 'name' | 'password'>
const admin: AdminUserInfo = {
name: 'Mick',
password: '123456',
}
答案
interface UserInfo {
name: string;
password: string;
phone: string;
}
type myPick3<T, K extends keyof T> = {
[P in K]: T[P];
};
type AdminUserInfo = myPick3<UserInfo, 'name' | 'password'>
3. 实现TS内置的 Exclude<T, U>
常用于处理联合类型
基本使用
1) 当 T 是联合类型,U 不是联合类型
type T = 'a' | 'b' | 'c';
type U = 'b';
type Result1 = Exclude<T, U>; // 结果是 'a' | 'c'
let x: Result1;
x = 'a'; // 正确
x = 'b'; // 错误:'b' 不在 'a' | 'c' 类型中
2) 当 T 不是联合类型,U 是联合类型
type T = 'a';
type U = 'b' | 'c';
type Result2 = Exclude<T, U>; // 结果是 'a',因为 'a' 不能赋值给 'b' 或 'c'
let y: Result2;
y = 'a'; // 正确
y = 'b'; // 错误:'b' 不在 'a' 类型中
3) 当 T 和 U 都不是联合类型
type T = 'a';
type U = 'b';
type Result3 = Exclude<T, U>; // 结果是 'a',因为 'a' 不能赋值给 'b'
let z: Result3;
z = 'a'; // 正确
z = 'b'; // 错误:'b' 不在 'a' 类型中
// TS相关 实现TS内置的 Exclude<T, U>
// 不能直接用原生TS的 Exclude
// 例如:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
答案
type MyExclude<T, U > = T extends U ? never : T
type Result2 = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
4. 实现一个Includes<T, U>类型处理
// TS相关 实现一个Includes<T, U>类型处理
// 类似 JavaScript 的 Array.includes 方法,
// 接受两个参数,返回的类型要么是 true 要么是 false。
type MyType1 = Includes<['A', 'B', 'C', 'D'], 'A'> // expected to be `true`
type MyType2 = Includes<['A', 'B', 'C', 'D'], 'E'> // expected to be `false`
答案
type myIncludes<T extends any[], K> = T extends (infer U)[] ? (K extends U ? true : false) : false;
type test55 = myIncludes<['A', 'B', 'C', 'D'], 'A'>
5. 实现TS内置的 Readonly
// TS相关 实现TS内置的 Readonly<T>
// 不能直接用原生TS的 Readonly
interface UserInfo {
name: string;
phone: string;
}
const user: MyReadonly<UserInfo> = {
name: 'Apple'
phone: '12345678';
}
user.name = 'Banana' // 报错 Error: cannot reassign a readonly property
答案
// TS相关 实现TS内置的 Readonly<T>
// 不能直接用原生TS的 Readonly
interface UserInfo {
name: string;
phone: string;
}
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
const user: MyReadonly<UserInfo> = {
name: 'Apple',
phone: '12345678'
}
user.name = 'Banana' // 报错 Error: cannot reassign a readonly property
6. 实现 LastItemType,接受一个数组T并返回其最后一个元素
// TS相关 实现 LastItemType<T>,接受一个数组T并返回其最后一个元素
type arr1 = [1, 2, 3]
type arr2 = ['a', 'b', 'c']
type last1 = LastItemType<arr1> // 表示类型为元素 3
type last2 = LastItemType<arr2> // 表示类型为元素 'c'
答案
type arr1 = [1, 2, 3]
type FirstElement<T extends any[]> =
T extends [...infer Rest, infer Last] ? Last : never;
type test = FirstElement<arr1>
7. 实现TS内置的 Omit<T, K>
用于从类型T排除一组属性K
// TS相关 实现TS内置的 Omit<T, K>
// 不能直接用原生TS的 Omit
interface UserInfo {
name: string;
password: string;
phone: string;
}
type User = MyOmit<UserInfo, 'password' | 'phone'>
const user: User = {
name: 'Hello'
}
答案
interface UserInfo {
name: string;
password: string;
phone: string;
}
type MyOmit<T , U extends keyof T> = Pick<T, Exclude<keyof T, U>>
type User = MyOmit<UserInfo, 'password' | 'phone'>
8. 元组转换为对象
将一个元组类型转换为对象类型,这个对象类型的键/值和元组中的元素对应。
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'}
答案
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: P
}
type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
9. 获取元组长度
创建一个Length泛型,这个泛型接受一个只读的元组,返回这个元组的长度。
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 tesla = ['tesla', 'model 3', 'model X', 'model Y']
type Length<T extends any[]> = T['length']
10. Awaited
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
答案
11. IF Concat Push Unshift
实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T
,以及一个判断为假时的返回类型 F。
C 只能是 true 或者 false, T 和 F 可以是任意类型。
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
// 在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,
// 返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
type Result = Concat<[1], [2]> // expected to be [1, 2]
//在类型系统里实现通用的 Array.push 。
type Result = Push<[1, 2], '3'> // [1, 2, '3']
// 实现类型版本的 Array.unshift。
type Result = Unshift<[1, 2], 0> // [0, 1, 2]
答案
// IF
type If<R, A, B> = R extends true ? A : B
type A = If<true, 'a', 'b'> // expected to be 'a'
//Concat
type Concat<T extends any[], K extends any[]> = [...T, ...K]
// Push
// 在类型系统里实现通用的 Array.push
type Push<A extends any[], K> = [...A, K]
type Result = Push<[1, 2], '3'> // [1, 2, '3']
// unshift
type Unshift<A extends any[], K> = [K, ...A]
12. 实现内置的 Parameters 类型
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
答案
type MyParameters<F> = F extends (...arg: infer G) => infer R ? G : never
13. 对象部分属性只读
实现一个泛型MyReadonly2<T, K>,它带有两种类型的参数T和K。
类型 K 指定 T 中要被设置为只读 (readonly) 的属性。如果未提供K,
则应使所有属性都变为只读,就像普通的Readonly<T>一样。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type test2 = MyReadonly2<Todo, "title" | "description">;
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
答案
type MyReadonly2<T, U extends keyof T> = {
readonly [P in U]: T[P];
} & Pick<T, Exclude<keyof T, U>>;
14. 对象属性只读(递归)
// 实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
// 您可以假设在此挑战中我们仅处理对象。
// 不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
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`
答案
在这里插入代码片
15. 对象属性只读(递归)
实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
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`
答案
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
16. 元组转联合类型
实现泛型TupleToUnion<T>,它返回元组所有值的合集。
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
答案
方式一:
type TupleToUnion<T extends any[]> = T extends (infer U)[] ? U : never
方式二:
type TupleToUnion<T extends any[]> = T extends any[] ? T[number] : never
17. 排除最后一项
实现一个泛型Pop<T>,它接受一个数组T,并返回一个由数组T的前 N-1 项(N 为数组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]
答案
type Pop<T> = T extends [...infer Other, infer last] ? Other : never
18. 可串联构造器 中等题
在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
例如
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// 期望 result 的类型是:
interface Result {
foo: number
name: string
bar: {
value: string
}
}
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。
答案
type Chainable<T extends object = {}> = {
option<K extends string, V>(key: K, value: V): Chainable<T & { [P in K]: V }>;
get(): T;
};
19. 查找类型
有时,您可能希望根据某个属性在联合类型中查找类型。
在此挑战中,我们想通过在联合类型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, K extends string, C = []>
= T extends Record<'type', K> ? T : never
20. 去除左侧空白
去除左侧空白
实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
例如
type trimed = TrimLeft<' Hello World '> // 应推导出 'Hello World '
答案
type TrimLeft<S> = S extends `${' '}${infer B}` ? TrimLeft<B> : S
21. 去除两端空白字符
实现Trim<T>,它接受一个明确的字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
例如
type trimed = Trim<' Hello World '> // expected to be 'Hello World'
答案
type Trim<T extends string> = T extends `${' '}${infer B}${' '}` ? Trim<B> : T
22. 实现Capitalize 将第一个字符转成大写 中等
// Capitalize
// 实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。
// 例如
type capitalized = Capitalize2<'hello world'> // expected to be 'Hello world'
答案
type Capitalize2<S extends string> = S extends `${infer x}${infer tail}` ? `${Uppercase<x>}${tail}` : S;
23. Replace
实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。
例如
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'
答案
type Replace<S extends string, A extends string, B extends string> =
S extends `${infer frond}${A}${infer end}` ? `${frond}${B}${end}` : never
24. ReplaceAll
ReplaceAll
实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。
例如
type replaced = ReplaceAll<' t y p e s ', ' ', ''> // 期望是 'types'
答案
type ReplaceAll<T extends string, K extends string, P extends string> =
T extends `${infer C}${K}${infer D}` ? ReplaceAll<`${C}${D}`, K, P> : T
25. Permutation联合类型转成数组全排列的联合类型 中等
// 实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
type perm = Permutation<'A' | 'B' | 'C'>;
// ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
答案
type Permutation<T, U = T> = [T] extends [never] // 检查 T 是否是 never 类型
? []
: (T extends U // 检查 T 是否是 U 的一个成员.如果不是 返回空数组 []
? [T, ...Permutation<Exclude<U, T>>] // 它试图生成不包含当前元素 T 的所有其他元素的排列,并将这些排列与当前元素 T 组合起来
: [])
26. 函数追加参数 中等
// 实现一个泛型 AppendArgument<Fn, A>,
// 对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。
// G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
答案
type AppendArgument<T extends (...arg) => {}, B> =
T extends (...arg: infer G) => infer R ? (...x: [...G, B]) => R : never // “扩展”或“包裹”G这个元组
27. 实现内置的Record
// 1. 使用字符串字面量作为键
type PersonRecord = MyRecord<'name' | 'age', string>;
const person: PersonRecord = {
name: 'Alice',
age: '30' // 注意:这里 age 实际上应该是 number 类型,但为了演示 Record 的使用,我们使用了 string
};
// 2. 使用枚举作为键
enum Keys {
Name = 'name',
Age = 'age'
}
type PersonRecordWithEnum = MyRecord<Keys, string>;
const personWithEnum: PersonRecordWithEnum = {
[Keys.Name]: 'Bob',
[Keys.Age]: '25'
};
// 3. 使用数字作为键(不常见,但可能)
type NumberRecord = MyRecord<1 | 2 | 3, string>;
const numbers: NumberRecord = {
1: 'one',
2: 'two',
3: 'three'
};
// 尝试使用非 1、2、3 的键会导致类型错误
// const numbers: NumberRecord = { 1: 'one', 2: 'two', 4: 'four' }; // 错误
// 4. 使用泛型
type KeyValuePair<K extends string, T> = MyRecord<K, T>;
function createObject<K extends string, T>(key: K, value: T): KeyValuePair<K, T> {
return { [key]: value } as any; // 这里使用 as any 是为了绕过 TypeScript 的限制,实际上应该使用更安全的方式来确保 key 是有效的属性名
}
const result = createObject('key', 'value'); // result 的类型是 { key: string }
答案
type MyRecord<T extends keyof any, K> = {
[P in T]: K
}
28. MyAwaited获取泛型参数
// 假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型
// 。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise
// 返回的类型。请你实现一个类型,可以获取这个类型。
// 例如:Promise<ExampleType>,请你返回 ExampleType 类型。
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
答案
type MyAwaited<T> = T extends Promise<infer U> ? U : never
29. Promise.all 中等
// Promise.all
// 给函数PromiseAll指定类型,它接受元素为 Promise
// 或者类似 Promise 的对象的数组,返回值应为Promise<T>,
// 其中T是这些 Promise 的结果组成的数组。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// 应推导出 `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)
答案
declare function PromiseAll<T extends any[]>(values: readonly [...T]):
Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;
30. Flatten 扁平化数组
// 在这个挑战中,你需要写一个接受数组的类型,
// 并且返回扁平化的数组类型。
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, 5]
答案
type Flatten<
T extends any[],
U extends any[] = [],
F extends any[] = []
> = T extends [infer first, ...infer other]
? Flatten<
other,
first extends any[] ? [...U, ...F] : [...U, ...F, first],
first extends any[] ? Flatten<first> : []
>
: [...U, ...F];