知识点梳理
- 类型收窄 extends
- 类型收窄也可以给默认值
- K extends keyof T = keyof T // 如果K不存在就把keyof T当默认值给K
- in操作符进行mapping映射
- as进行remapping重映射
```typescript
type Remap
= {
}
- 元组T通过T[number]可以获取元组中所有类型的联合类型union
```typescript
const Gender = ["Male","Female"] as const
type 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
// 获取返回值
// number
type 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 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2
答案
# 思路
1. 首先类型收窄限定泛型参数必须是一个函数 extends
2. 使用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-error
type error = MyOmit<Todo, 'description' | 'invalid'>
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
completed: 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: string
description?: string
completed: boolean
}
interface Todo2 {
readonly title: string
description?: string
completed: boolean
}
interface Expected {
readonly title: string
readonly description?: string
completed: boolean
}
// ============= Your Code Here =============
# 思路
1. 类型收窄限定K必须在T里面,如果K不存在就给默认值keyof T
2. 如果在K里面的通过in遍历加上readonly
3. 如果不在k里面的通过交叉类型保持不变
// 上面已经实现过的MyOmit
type 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: () => 22
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: 'string'
}
k: 'hello'
}
l: [
'hi',
{
m: ['hey']
},
]
}
}
}
type Expected = {
readonly a: () => 22
readonly b: string
readonly c: {
readonly d: boolean
readonly e: {
readonly g: {
readonly h: {
readonly i: true
readonly j: 'string'
}
readonly k: 'hello'
}
readonly l: readonly [
'hi',
{
readonly m: readonly ['hey']
},
]
}
}
}
答案
# 思路
1. 首先肯定要用到递归,处理子类型中的readonly
2. 通过in遍历传入泛型的所有属性
3. 通过extends进行类型收窄判断,基本类型直接返回类型,除函数外的对象类型递归处理
// 需要注意 type isObject = Function extends Object?true:false // true
// 函数也是对象类型,但是上面例子函数不需要递归处理
type ExcludeType = string | number | symbol | Function
type 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: Chainable
const 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: number
bar: {
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. 通过类型解构拿到数组中所有的参数类型T
3. 返回值必须拿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 + infer
type 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条件类型 + infer
type 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) => number
type Case2 = AppendArgument<() => void, undefined>
type Result2 = (x: undefined) => void
type 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;