知识点梳理
- 类型收窄 extends
- 类型收窄也可以给默认值
- K extends keyof T = keyof T // 如果K不存在就把keyof T当默认值给K
- in操作符进行mapping映射
- as进行remapping重映射
```typescript
type Remap
= {
}
- 元组T通过T[number]可以获取元组中所有类型的联合类型union```typescriptconst Gender = ["Male","Female"] as consttype genderUnion = Gender[number] // Male | Female
- 判断一个字符串字面量是否含有另一个字符串字面量 ```typescript // 模板字符串我们能够判断一个字符串中是否存在某一个其他字符
type ReplaceTest = “ABCD” extends ${infer Prefix}${"BC"}${infer Suffix}
? ${Prefix}${Suffix}
: never;
// AD ts中没法通过正则匹配,只能通过模板字符串
- 通过 ...arg 和 infer 相互配合能够用一个数组提取出函数的所有入参```typescript// 获取所有的参数 [ a:number,b:string ]type GetArgs = ((a: number, b: string) => number) extends (...arg:infer Args)=>infer Result ? Args : never// 获取返回值// numbertype GetResult = ((a: number, b: string) => number) extends (...arg:infer Args)=>infer Result ? Result : never
刷题开始
[1] ReturnType
实现内置的ReturnType类型,获取函数的返回值
type cases = [Expect<Equal<string, MyReturnType<() => string>>>,Expect<Equal<123, MyReturnType<() => 123>>>,Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,]type ComplexObject = {a: [12, 'foo']bar: 'hello'prev(): number}const fn = (v: boolean) => v ? 1 : 2const fn1 = (v: boolean, w: any) => v ? 1 : 2
答案
# 思路1. 首先类型收窄限定泛型参数必须是一个函数 extends2. 使用infer获取函数的返回值type MyReturnType<T extends Function> = T extends (...args: any[]) => infer R ? R : never;
[2] Omit
实现内置的Omit方法
type cases = [Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,]// @ts-expect-errortype error = MyOmit<Todo, 'description' | 'invalid'>interface Todo {title: stringdescription: stringcompleted: boolean}interface Expected1 {title: stringcompleted: boolean}interface Expected2 {title: string}
答案
# 思路1. 类型收窄,限定需要omit的联合类型属性R肯定在keyof T中2. 需要在使用in操作符mapping的时候使用as remapping把在R中的属性去掉type MyOmit<T, R extends keyof T> = {[P in keyof T as P extends R ? never : P]: T[P]}
[3] Readonly2
实现一个MyReadonly
接受两个泛型参数T,K,K是T中需要设置成readonly的属性集,如果K没有则T中所有的属性都是readonly,等价于MyReadonly
// ============= Test Cases =============import type { Alike, Expect } from './test-utils'type cases = [Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,]interface Todo1 {title: stringdescription?: stringcompleted: boolean}interface Todo2 {readonly title: stringdescription?: stringcompleted: boolean}interface Expected {readonly title: stringreadonly description?: stringcompleted: boolean}// ============= Your Code Here =============# 思路1. 类型收窄限定K必须在T里面,如果K不存在就给默认值keyof T2. 如果在K里面的通过in遍历加上readonly3. 如果不在k里面的通过交叉类型保持不变// 上面已经实现过的MyOmittype MyOmit<T, R extends keyof T> = {// 通过as来remapping[P in keyof T as P extends R ? never : P]: T[P];};type MyReadonly2<T, K extends keyof T = keyof T> = MyOmit<T, K> & {readonly [P in K]: T[P];};
[4] deep readonly
所有属性和子属性递归readonly
type cases = [Expect<Equal<DeepReadonly<X>, Expected>>,]type X = {a: () => 22b: stringc: {d: booleane: {g: {h: {i: truej: 'string'}k: 'hello'}l: ['hi',{m: ['hey']},]}}}type Expected = {readonly a: () => 22readonly b: stringreadonly c: {readonly d: booleanreadonly e: {readonly g: {readonly h: {readonly i: truereadonly j: 'string'}readonly k: 'hello'}readonly l: readonly ['hi',{readonly m: readonly ['hey']},]}}}
答案
# 思路1. 首先肯定要用到递归,处理子类型中的readonly2. 通过in遍历传入泛型的所有属性3. 通过extends进行类型收窄判断,基本类型直接返回类型,除函数外的对象类型递归处理// 需要注意 type isObject = Function extends Object?true:false // true// 函数也是对象类型,但是上面例子函数不需要递归处理type ExcludeType = string | number | symbol | Functiontype DeepReadonly<T> = {readonly [P in keyof T]: T[P] extends ExcludeType ? T[P] : DeepReadonly<T[P]>}
[5]Tuple to Union

把一个
**[Tuple]元组类型**转换成**Union联合类型**
type Arr = ['1', '2', '3']type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
答案
# 思路:通过 number 直接可以将元组类型转换为联合类型type TupleToUnion<Arr extends unknown[]> = Arr[number]
[6]Chainable options
实现一个链式options类型,option进行配置类型,通过get获取所有的配置类型
declare const a: Chainableconst result1 = a.option('foo', 123).option('bar', { value: 'Hello World' }).option('name', 'type-challenges').get()const result2 = a.option('name', 'another name')// @ts-expect-error.option('name', 'last name').get()type cases = [Expect<Alike<typeof result1, Expected1>>,Expect<Alike<typeof result2, Expected2>>,]type Expected1 = {foo: numberbar: {value: string}name: string}type Expected2 = {name: string}
答案:
# 思路1. 首先约束一下结果Result必须是一个对象,给一个默认值{}2. option接受两个泛型参数key和value,通过K extends keyof Result判断key不能重复出现在result中3. 因为是链式,所以option的返回结果也是一个Chainable,但是此时的泛型参数已经是Result结果+新传入的option的交集了type Chainable<Result extends Record<string, unknown> = {}> = {option: <K extends string, V>(key: K extends keyof Result ? never : K, value: V) => Chainable<Result & {[P in K]: V}>get: () => Result}
[7] last of array
实现一个返回数组最后一个类型的工具
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
答案
# 思路1. 通过extends条件类型 + infer 获取数组的最后一位type LastOfArray<T> = T extends [... infer Rest, infer Last] ? Last: never
[8] Pop
实现一个类似js的pop去掉最后一位的一个类型的工具
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]
答案
# 思路1. 通过extends条件类型 + infer 获取数组的最后一位type Pop<T> = T extends [... infer Rest, infer Last] ? Rest: never
[9] PromiseAll
实现一个类似js的Pormise.all
const promiseAllTest1 = PromiseAll([1, 2, 3] as const)const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])type cases = [Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,]
答案
# 思路1. 类型收窄限定T必须是一个数组2. 通过类型解构拿到数组中所有的参数类型T3. 返回值必须拿Promise包裹4. 包裹的属性通过in遍历一下,如果还是一个promise的话继续通过infer取出里面的类型,否则直接返回类型5. 个人感觉本地只有两层promise,实际上应该通过递归处理// ============= Your Code Here =============declare function PromiseAll<T extends any[] = any[]>(values: readonly [...T]): Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;
[10] Type lookup
从联合类型中通过type属性查找到目标类型
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`
答案
# 思路1. 分布式条件判断extends + infertype LookUp<U, T extends string> = U extends {type: infer R} ? R extends T ? U : never : never
[11] Trim left
移除字符串左侧的空白
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World 'type cases = [Expect<Equal<TrimLeft<'str'>, 'str'>>,Expect<Equal<TrimLeft<' str'>, 'str'>>,Expect<Equal<TrimLeft<' str'>, 'str'>>,Expect<Equal<TrimLeft<' str '>, 'str '>>,Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>,Expect<Equal<TrimLeft<''>, ''>>,Expect<Equal<TrimLeft<' \n\t'>, ''>>,]
答案
# 思路1. extends 进行模式匹配,然后递归去空格// 本来写的答案是这个,没有考虑转义字符type TrimLeft<S extends string> = S extends ` ${infer Rest}`? TrimLeft<Rest>: S;// case中包含转移自付type TrimLeft<S extends string> = S extends `${" " | "\n" | "\t"}${infer Rest}`? TrimLeft<Rest>: S;
[12] Trim right
移除字符串右侧的空白
type trimed = TrimRight<' Hello World '> // expected to be 'Hello World 'type cases = [Expect<Equal<TrimRight<'str'>, 'str'>>,Expect<Equal<TrimRight<'str '>, 'str'>>,Expect<Equal<TrimRight<'str '>, 'str'>>,Expect<Equal<TrimRight<' str '>, ' str'>>,Expect<Equal<TrimRight<'\n\t foo bar '>, ' foo bar'>>,Expect<Equal<TrimRight<''>, ''>>,Expect<Equal<TrimRight<'\n\t '>, ''>>,]
答案
# 思路1. 通过extends条件类型 + infer 获取数组的最后一位type TrimRight<S extends string> = S extends `${infer Rest}${" " | "\n" | "\t"}`? TrimRight<Rest>: S;
[13] Trim
移除字符串两侧的空白
type cases = [Expect<Equal<Trim<'str'>, 'str'>>,Expect<Equal<Trim<' str'>, 'str'>>,Expect<Equal<Trim<' str'>, 'str'>>,Expect<Equal<Trim<'str '>, 'str'>>,Expect<Equal<Trim<' str '>, 'str'>>,Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,Expect<Equal<Trim<''>, ''>>,Expect<Equal<Trim<' \n\t '>, ''>>,]
答案
# 思路1. 左侧和右侧实现的方法进行组合type Trim<S extends string> = TrimLeft<TrimRight<S>>;
[15] Capitalize
首字母大写
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
答案
# 思路1. 通过extends条件类型 + infertype MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}`?`${Uppercase<F>}${Rest}`:S
[16] Replace
实现类似js的replace
type cases = [Expect<Equal<Replace<"foobar", "bar", "foo">, "foofoo">>,Expect<Equal<Replace<"foobarbar", "bar", "foo">, "foofoobar">>,Expect<Equal<Replace<"foobarbar", "", "foo">, "foobarbar">>,Expect<Equal<Replace<"foobarbar", "bar", "">, "foobar">>,Expect<Equal<Replace<"foobarbar", "bra", "foo">, "foobarbar">>,Expect<Equal<Replace<"", "", "">, "">>];
答案
# 思路1. 通过extends 类型收窄 限定都是字符串2. 通过模板字符串进行匹配3. 根据要求把目标字符串进行替换type Replace<S extends string,From extends string,To extends string> = S extends `${infer Prefix}${From}${infer Suffix}`? From extends ""? S: `${Prefix}${To}${Suffix}`: S;
[17] ReplaceAll
替换全部的From为To
type cases = [Expect<Equal<ReplaceAll<"foobar", "bar", "foo">, "foofoo">>,Expect<Equal<ReplaceAll<"foobar", "bag", "foo">, "foobar">>,Expect<Equal<ReplaceAll<"foobarbar", "bar", "foo">, "foofoofoo">>,Expect<Equal<ReplaceAll<"t y p e s", " ", "">, "types">>,Expect<Equal<ReplaceAll<"foobarbar", "", "foo">, "foobarbar">>,Expect<Equal<ReplaceAll<"barfoo", "bar", "foo">, "foofoo">>,Expect<Equal<ReplaceAll<"foobarfoobar", "ob", "b">, "fobarfobar">>,Expect<Equal<ReplaceAll<"foboorfoboar", "bo", "b">, "foborfobar">>,Expect<Equal<ReplaceAll<"", "", "">, "">>];
答案
# 思路1. 上面已经实现了replace,替换第一个2. 替换全部,也就是对目标以外的prefix和suffix递归调用一下replace方法type ReplaceAll<S extends string,From extends string,To extends string> = S extends `${infer Prefix}${From}${infer Suffix}`? From extends ""? S: `${ReplaceAll<Prefix, From, To>}${To}${ReplaceAll<Suffix, From, To>}`: S;
Append Argument
实现一个泛型 AppendArgument
,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
type Case1 = AppendArgument<(a: number, b: string) => number, boolean>type Result1 = (a: number, b: string, x: boolean) => numbertype Case2 = AppendArgument<() => void, undefined>type Result2 = (x: undefined) => voidtype cases = [Expect<Equal<Case1, Result1>>,Expect<Equal<Case2, Result2>>,]
答案
# 思路1. 通过类型解构+infer先获取原函数的参数和返回值2. 再将新的参数拼接到原始参数之后,返回默认的返回值type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer arguments) => infer Result? (...args: [...arguments, A]) => Result: never;

