wuxin0011`blog wuxin0011`blog
首页
  • 基础内容

    • HTML
    • CSS
    • JavaScript
  • 进阶

    • JavaScript教程
    • ES6 教程
    • Vue
    • React
    • Typescript
    • Git
  • 推荐阅读
  • java随笔
  • JVM (opens new window)
  • 分类
  • 标签
  • 归档
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • git使用
  • docker部署项目
  • 错误收集
  • github-start
  • background-color
  • live-server
  • vscode-cpp-config
  • tampermonkey-script (opens new window)
  • leetcode-tool (opens new window)
  • 网站
  • 前端
  • 后端
  • Github Topic (opens new window)
GitHub (opens new window)

wuxin0011

懂得越多,懂得越少
首页
  • 基础内容

    • HTML
    • CSS
    • JavaScript
  • 进阶

    • JavaScript教程
    • ES6 教程
    • Vue
    • React
    • Typescript
    • Git
  • 推荐阅读
  • java随笔
  • JVM (opens new window)
  • 分类
  • 标签
  • 归档
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • git使用
  • docker部署项目
  • 错误收集
  • github-start
  • background-color
  • live-server
  • vscode-cpp-config
  • tampermonkey-script (opens new window)
  • leetcode-tool (opens new window)
  • 网站
  • 前端
  • 后端
  • Github Topic (opens new window)
GitHub (opens new window)
  • 基础

  • 进阶

    • TypeScript训练
      • 说在前面
      • 简单系列
        • 🚪 实现元组转换对象
        • 🚪 实现 exclude
        • 🚪 实现 Include
        • 🚪 实现 If
      • 数组系列
        • 🚪 实现 First
        • 🚪 实现 End
        • 🚪 实现 Unshitf
        • 🚪 实现 Shitf
        • 🚪 实现 Push
        • 🚪 获取 Length
        • 🚪 实现 concat
        • 🚪 实现 Reverse
        • Ⓜ️ 实现 LastIndexOf
        • Ⓜ️ 实现 Combination
        • Ⓜ️ 实现 Filter
        • 🚪 实现 Unique
        • 🚪 实现 GetMiddleElement
        • Ⓜ️ 实现 Appear only once
        • 🚪 实现数组扁平化
        • 🚪 指定深度扁平化
        • Ⓜ️ 实现指定删除数组内容
        • Ⓜ️ 实现给定长度的数组
        • 🍅 实现矩阵变换
      • ReadOnly 系列
        • 🚪 实现 Readonly
        • Ⓜ️ 实现 DeepReadOnly
        • Ⓜ️ 实现 Mutable
        • Ⓜ️ 实现 DeepMutable
      • 字符串
        • 🚪 实现字符串首字母大写
        • 🚪 实现字符串替换
        • 🚪 实现获取字符串长度
        • 🚪 实现字符串转换联合类型
        • 🚪 实现是否是给定字符串开头
        • 🚪 实现去掉字符串空格
        • 🚪 实现删除符合要求的字符串
        • Ⓜ️ 实现驼峰命名转换短横线命名
        • Ⓜ️ 现判断一个字符串中字符是否重复
        • Ⓜ️ 实现 单词首字母大写
        • 🍅 实现 CamelCase
        • 🍅 实现 LengthOfString
      • 函数系列
        • 🚪 实现获取函数返回类型
        • Ⓜ️ 实现函数参数反转
        • 🍅 实现参数追加
        • 🍅 实现柯里化
      • Object 系列
        • 🚪 实现为一个接口添加新类型
        • Ⓜ️ 实现连两个对象合并
        • Ⓜ️ 实现可配置可选链
        • Ⓜ️ 实现 Diff
        • Ⓜ️ 实现属性替换
        • 🤔 实现移除索引签名
        • 🤔 实现 Entries
        • 🤔 实现 元组递归转换成对象
        • 🤔 实现 将元组转换成枚举
      • Is 系列
        • 🍅 实现 IsNever
        • 🤔 实现 isUnion
        • ❓ 实现 IsAny
        • 🍅 实现 IsAnyof
        • 🍅 实现 IsRequiredKey
        • 🍅 实现 IsPalindrome
      • 中等系列
        • Ⓜ️ 实现 Omit
        • Ⓜ️ 实现元组转换成联合类型
        • Ⓜ️ 实现 Absolute
        • 🤔 实现 MinusOne
        • Ⓜ️ 实现 PickByType
        • 🍅 实现 PartialByKeys
        • Ⓜ️ 实现 OmitByType
        • Ⓜ️ Bem 架构
        • 🍅 指定返回的联合类型
      • 困难系列
        • 🍅 实现 GetRequired
        • 🍅 实现 GetRequiredkeys
        • 🍅 实现 GetOptional
        • 🍅 实现 GetOptionalKeys
        • 🍅 实现 SimpleVue
        • 🍵 实现将字符串转换成数字
        • 🤔 实现两数之和
        • 📅 实现日期验证
        • 🍰 实现 Get
        • 🍅 实现 Maximum
      • 地狱
        • 🚀 实现 Equal
        • 🚀 实现 slice
        • 🚀 获取只读字段
        • 🚀 实现柯理化2
        • 🚀 实现 QueryStringParser
        • 🚀 实现 JSON Parse ❓
      • 总结
      • 相关链接
  • 《Typescript》笔记
  • 进阶
wuxin0011
2023-05-05
目录

TypeScript训练

# 说在前面

训练题目来源 ts训练营 (opens new window)

关于标记和题目难易度说明

  • 🚀 表示地狱级别

  • 🍅 表示是难题

  • 🚪 表示简单级别题目

  • Ⓜ️ 表示中等题目

  • ❌ 表示题目未处理

  • 🤔 表示题目值得思考

  • ❓ 表示还有疑问

  • 🍵 表示题目值得放松一下的简单题

  • 其他符号没什么含义 ?嗯... 【我肯定是这样认为的】

  • 题目难易度不一定是从简单到复杂的,有些题目放在一起是为了方便归类。


  • 说在前面
  • 简单系列
    • 🚪 实现元组转换对象
    • 🚪 实现 exclude
    • 🚪 实现 Include
    • 🚪 实现 If
  • 数组系列
    • 🚪 实现 First
    • 🚪 实现 End
    • 🚪 实现 Unshitf
    • 🚪 实现 Shitf
    • 🚪 实现 Push
    • 🚪 获取 Length
    • 🚪 实现 concat
    • 🚪 实现 Reverse
    • Ⓜ️ 实现 LastIndexOf
    • Ⓜ️ 实现 Combination
    • Ⓜ️ 实现 Filter
    • 🚪 实现 Unique
    • 🚪 实现 GetMiddleElement
    • Ⓜ️ 实现 Appear only once
    • 🚪 实现数组扁平化
    • 🚪 指定深度扁平化
    • Ⓜ️ 实现指定删除数组内容
    • Ⓜ️ 实现给定长度的数组
    • 🍅 实现矩阵变换
  • ReadOnly 系列
    • 🚪 实现 Readonly
    • Ⓜ️ 实现 DeepReadOnly
    • Ⓜ️ 实现 Mutable
    • Ⓜ️ 实现 DeepMutable
  • 字符串
    • 🚪 实现字符串首字母大写
    • 🚪 实现字符串替换
    • 🚪 实现获取字符串长度
    • 🚪 实现字符串转换联合类型
    • 🚪 实现是否是给定字符串开头
    • 🚪 实现去掉字符串空格
    • 🚪 实现删除符合要求的字符串
    • Ⓜ️ 实现驼峰命名转换短横线命名
    • Ⓜ️ 现判断一个字符串中字符是否重复
    • Ⓜ️ 实现 单词首字母大写
    • 🍅 实现 CamelCase
    • 🍅 实现 LengthOfString
  • 函数系列
    • 🚪 实现获取函数返回类型
    • Ⓜ️ 实现函数参数反转
    • 🍅 实现参数追加
    • 🍅 实现柯里化
  • Object 系列
    • 🚪 实现为一个接口添加新类型
    • Ⓜ️ 实现连两个对象合并
    • Ⓜ️ 实现可配置可选链
    • Ⓜ️ 实现 Diff
    • Ⓜ️ 实现属性替换
    • 🤔 实现移除索引签名
    • 🤔 实现 Entries
    • 🤔 实现 元组递归转换成对象
    • 🤔 实现 将元组转换成枚举
  • Is 系列
    • 🍅 实现 IsNever
    • 🤔 实现 isUnion
    • ❓ 实现 IsAny
    • 🍅 实现 IsAnyof
    • 🍅 实现 IsRequiredKey
    • 🍅 实现 IsPalindrome
  • 中等系列
    • Ⓜ️ 实现 Omit
    • Ⓜ️ 实现元组转换成联合类型
    • Ⓜ️ 实现 Absolute
    • 🤔 实现 MinusOne
    • Ⓜ️ 实现 PickByType
    • 🍅 实现 PartialByKeys
    • Ⓜ️ 实现 OmitByType
    • Ⓜ️ Bem 架构
    • 🍅 指定返回的联合类型
  • 困难系列
    • 🍅 实现 GetRequired
    • 🍅 实现 GetRequiredkeys
    • 🍅 实现 GetOptional
    • 🍅 实现 GetOptionalKeys
    • 🍅 实现 SimpleVue
    • 🍵 实现将字符串转换成数字
    • 🤔 实现两数之和
    • 📅 实现日期验证
    • 🍰 实现 Get
    • 🍅 实现 Maximum
  • 地狱
    • 🚀 实现 Equal
    • 🚀 实现 slice
    • 🚀 获取只读字段
    • 🚀 实现柯理化2
    • 🚀 实现 QueryStringParser
    • 🚀 实现 JSON Parse ❓
  • 总结
  • 相关链接


# 简单系列

实现 Pick

interface Todo {
    title: string
    description: string
    completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}
1
2
3
4
5
6
7
8
9
10
11
12

提示

  • in
  • keyof
  • extends
点击查看答案
type MyPick<T, K extends keyof T> = {

    [key in K]: T[key]

}
1
2
3
4
5

# 🚪 实现元组转换对象

传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。

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'}
1
2
3

错误示例

点击查看
type TupleToObject<T extends any[]> = {
    [K in T[number]]: K
}
1
2
3

正确答案

点击查看答案
type TupleToObject<T extends (number | symbol | string)[]> = {
    [K in T[number]]: K
}
1
2
3

# 🚪 实现 exclude

提示

  • never
  • extends
点击查看答案
type MyExclude<T, U> = U extends T ? never : T
1

# 🚪 实现 Include

提示

  • never
  • extends
点击查看答案
type MyInclude<T, U> = U extends T ? T : never
1

# 🚪 实现 If

实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型。

example

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
1
2

提示

  • boolean
  • extends
点击查看答案
type If<T extends boolean, U, P> = T extends true ? U : P
1

# 数组系列

# 🚪 实现 First

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
1
2
3
4
5
6
点击查看答案
type First<T extends any[]> = T extends [infer F, ...infer] ? F : []
1

# 🚪 实现 End

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

// 
type head1 = End<arr1> // expected to be 'c'
type head2 = End<arr2> // expected to be 1
1
2
3
4
5
6
点击查看答案
type End<T extends any[]> = T extends [...infer, infer E] ? E : []
1

# 🚪 实现 Unshitf


type Result = Unshift<[1, 2], 0> // [0, 1, 2]

1
2
3
点击查看答案
type Unshift<T extends any[], U> = [U, ...T]
1

# 🚪 实现 Shitf

type Result = Shift<[1, 2, 2, 3]> // [2,3,3]

1
2
点击查看答案
type Shift<T extends any[]> = T extends [infer L,...infer R]?R:T
1

# 🚪 实现 Push


type Result = Push<[1, 2], 0> // [1,2,0]

1
2
3
点击查看答案
type Push<T extends any[], U> = [...T, U]
1

# 🚪 获取 Length

创建一个通用的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
1
2
3
4
5

提示

  • extends
  • ts中获取数组获取长度的方式
点击查看答案
type Length<T extends readonly any[]> = T['length']
1

# 🚪 实现 concat

提示

数组的解构赋值

点击查看答案
type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U];
1

# 🚪 实现 Reverse

type A = Reverse<[1, 2, 3, 4]> // [4, 3, 2, 1]
1

构造一个数组存放反转数组

点击查看答案
// 解法一
type Reverse<T extends any[], U extends any[] = []> = T extends [...infer L, infer R] ? Reverse<L, [...U, R]> : U

// 解法二
type Reverse<T extends any[]> = T extends [...infer F, infer R] ? [R, ...Reverse<F>] : T
1
2
3
4
5

# Ⓜ️ 实现 LastIndexOf

实现Array.lastIndexOf的类型版本,lastIndexOf<T,U>获取数组T,任意U,并返回数组T中最后一个U的索引

type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
1
2

递归遍历整个数组,使用变量保存下标

点击查看答案
type LastIndexOf<T extends any[], U extends number, M extends number = -1, N extends any[] = []> =
  T extends [infer A, ...infer B]
  ? A extends U
  ? LastIndexOf<B, U, N['length'], [...N, 0]> : LastIndexOf<B, U, M, [...N, 0]>
  : M
1
2
3
4
5

# Ⓜ️ 实现 Combination

// expected to be `"foo" | "bar" | "baz" | "foo bar" | "foo bar baz" | "foo baz" | "foo baz bar" | "bar foo" | "bar foo baz" | "bar baz" | "bar baz foo" | "baz foo" | "baz foo bar" | "baz bar" | "baz bar foo"`
type Keys = Combination<['foo', 'bar', 'baz']>
1
2

递归遍历整个数组

点击查看答案
type IsExist<T extends any[], O> =
  T extends [infer L, ...infer R]
  ? L extends O ? true : IsExist<R, O> : false



type Unique<T extends any[], N extends any[] = []> =
  T extends [infer L, ...infer R] ?
  IsExist<N, L> extends false ? Unique<R, [...N, L]>
  : Unique<R, N> : N


1
2
3
4
5
6
7
8
9
10
11
12

# Ⓜ️ 实现 Filter

type Filtered = FilterOut<[1, 2, null, 3], null> // [1, 2, 3]
1
点击查看答案

type FilterOut<T extends any[], U> = T extends [infer L, ...infer Rest] ? [L] extends [U] ? [...FilterOut<Rest, U>] : [L, ...FilterOut<Rest, U>] : T

1
2
3

本题解答地址 (opens new window)

// todo....

# 🚪 实现 Unique

数组去重

type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
1
2
3
4
5

递归遍历整个数组

点击查看答案
type IsExist<T extends any[], O> =
  T extends [infer L, ...infer R]
  ? L extends O ? true : IsExist<R, O> : false



type Unique<T extends any[], N extends any[] = []> =
  T extends [infer L, ...infer R] ?
  IsExist<N, L> extends false ? Unique<R, [...N, L]>
  : Unique<R, N> : N


1
2
3
4
5
6
7
8
9
10
11
12

# 🚪 实现 GetMiddleElement

如果是奇数长度,取中间一个数,如果是偶数,取中间两个数。

type simple1 = GetMiddleElement<[1, 2, 3, 4, 5]> // expected to be [3]
type simple2 = GetMiddleElement<[1, 2, 3, 4, 5, 6]> // expected to be [3, 4]
1
2

递归遍历整个数组,判断长度什么时候可以递归结束;以及使用infer如何提取中间内容

点击查看答案
type GetMiddleElement<T extends any[]> =
  T['length'] extends 0 | 1 | 2
  ? T : T extends [any, ...infer R, any] ? GetMiddleElement<R> :never
1
2
3

# Ⓜ️ 实现 Appear only once

查找目标数组中只出现一次的元素。例如:输入:[1,2,3,3,4,5,6,6,6],输出:[1,4,5]。

type simple1 = GetMiddleElement<[1, 2, 3, 4, 5]> // expected to be [3]
type simple2 = GetMiddleElement<[1, 2, 3, 4, 5, 6]> // expected to be [3, 4]
1
2

可以使用一个辅助类判断是否一个元素是否重复,重复不添加,反之添加

点击查看答案
// 辅助类 是否重复
type IsRepeated<T extends any[], U, N extends any[] = []> =
  T extends [infer L, ...infer R] ? L extends U
  ? IsRepeated<R, U, [...N, L]> : IsRepeated<R, U, N> : N['length'] extends 0 | 1 ? false : true

// 结果
type UniqueArray<T extends any[], O extends any[] = T, U extends any[] = []> =
  T extends [infer L, ...infer R] ?
  IsRepeated<O, L> extends true ? UniqueArray<R, O, U> : UniqueArray<R, O, [...U, L]> : U
1
2
3
4
5
6
7
8
9

# 🚪 实现数组扁平化

type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
1

递归

点击查看答案
// 解法一
type Flatten<T extends any[], O extends any[] = []> =
  T extends [infer F, ...infer R] ? (
    (F extends any[] ? Flatten<[...F, ...R], O> : Flatten<R, [...O, F]>)
  ) : O

// 解法二 不使用额外参数
type F<T extends any[]> =
  T extends [infer L, ...infer R]
  ? L extends any[] ? [...F<L>, ...F<R>] : [L, ...F<R>] : T
1
2
3
4
5
6
7
8
9
10

# 🚪 指定深度扁平化

type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
1
2

参考数组深度扁平化,可以利用数组 `length` 属性,实现指定长度判断

点击查看答案
// 先实现看看 深度递归扁平化
type FlattenDepth<T extends any[], U extends number = 1, D extends any[] = []> = 
T extends [infer L, ...infer R]
  ? (L extends any[] ? [...FlattenDepth<L, U, [...D, 1]>, ...FlattenDepth<R, U, D>] : [L, ...FlattenDepth<R, U, D>]) : T


// 加上长度判断就可以实现指定深度扁平化了
// 答案
type FlattenDepth<T extends any[], U extends number = 1, D extends any[] = []> = 
  D['length'] extends U
  ? T : T extends [infer L, ...infer R]
  ? (L extends any[] ? [...FlattenDepth<L, U, [...D, 1]>, ...FlattenDepth<R, U, D>] : [L, ...FlattenDepth<R, U, D>]) : T
1
2
3
4
5
6
7
8
9
10
11
12

# Ⓜ️ 实现指定删除数组内容

type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
1
2
3

解法有很多,判断内容是否存在与指定内容中,或者使用 联合类型,

点击查看答案
// 解法一 转换成联合类型
type ToUnion<T extends any> = T extends any[] ? T[number] : T
type Without<T extends any[], U extends any | any[]> = T extends [infer L, ...infer R] ?
  L extends ToUnion<U> ? Without<R, U> : [L, ...Without<R, U>] : T

// 解法二 判断法 
type IsExist<R, T extends any | any[]> =
  T extends [infer A, ...infer B] ? R extends A ? true : IsExist<R, B> : R extends T ? true : false


type Without<T extends any[], U, O extends any[] = []> =
  T['length'] extends 0 ? O
  :
  T extends [infer L, ...infer R]
  ?
  IsExist<L, U> extends true ? Without<R, U, O> : Without<R, U, [L, ...O]>
  : O


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

拓展

  • 尝试使用 Pop 或者 shift 方法实现!

# Ⓜ️ 实现给定长度的数组

type result = ConstructTuple<2> // expect to be [unknown, unkonwn]
1
点击查看答案
type ConstructTuple<T extends number, R extends any[] = []> = R['length'] extends T ? R : ConstructTuple<T, [...R, unknown]>
1

# 🍅 实现矩阵变换

type Matrix = Transpose <[[1]]>; // expected to be [[1]]
type Matrix1 = Transpose <[[1, 2], [3, 4]]>; // expected to be [[1, 3], [2, 4]]
type Matrix2 = Transpose <[[1, 2, 3], [4, 5, 6]]>; // expected to be [[1, 4], [2, 5], [3, 6]]
1
2
3
点击查看答案
type ConstructTuple<T extends number, R extends any[] = []> = R['length'] extends T ? R : ConstructTuple<T, [...R, unknown]>
1

# 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
1
2
3
4
5
6
7
8
9
10
11
12
点击查看答案
type MyReadonly<T> = {
    +readonly [key in keyof T]: T[key]
}
1
2
3

# Ⓜ️ 实现 DeepReadOnly

type X = {
    name: 'a',
    age: 12,
    b: {
        c: {
            a: 'role'
        }
    }
}

type Expected = {
    readonly x: {
        readonly a: 1
        readonly b: 'hi'
    }
    readonly y: 'hey'
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

提示

我个人感觉这种写法不好理解

  • keyof
  • in
  • extends
  • never
点击查看答案
type DeepReadonly<T> = keyof T extends never ? T : { readonly [k in keyof T]: DeepReadonly<T[k]> };
1

# Ⓜ️ 实现 Mutable

实现一个通用的类型 Mutable<T>,使类型 T 的全部属性可变(非只读)。

interface Todo {
  readonly title: string
  readonly description: string
  readonly completed: boolean
}

type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }

1
2
3
4
5
6
7
8

**+readonly** 表示只读, **-readonly** 表示可读写

点击查看答案
type Mutable<T extends object = {}> = {
  -readonly [key in keyof T]: T[key]
}
1
2
3

# Ⓜ️ 实现 DeepMutable

实现递归可读写

点击查看答案
type DeepReadonly<T> = keyof T extends never ? T : { -readonly [k in keyof T]: DeepReadonly<T[k]> };
1

# 字符串

# 🚪 实现字符串首字母大写

type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
1

`ts` 中字符串转换成大写 `Uppercase`;小写是 `Lowercase`

点击查看答案
type Capitalize<T extends string> = T extends `${infer F}${infer L}` ? `${Uppercase<F>}${L}` : T 

1
2

拓展

  • 实现 全部大写
  • 实现 全部小写

# 🚪 实现字符串替换

type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
1

使用infer提取,需要考虑空字符串情况

点击查看答案
type Replace<S extends string, F extends string, T extends string> = S extends '' ? S : (
    S extends `${infer L}${F}${infer R}`?  `${L}${T}${R}`: S
)
1
2
3

拓展

实现全局替换

type replaced = ReplaceAll<'fun types fun are fun!', 'fun', 'awesome'> // expected to be 'awesome types awesome are awesome!'
1

根据上面使用递归就可以了

点击查看答案
type ReplaceAll<S extends string, F extends string, T extends string> = S extends '' ? S : (
    S extends `${infer L}${F}${infer R}` ? ReplaceAll<`${L}${T}${R}`, F, T> : S
)
1
2
3

# 🚪 实现获取字符串长度

字符串是是无法直接获取长度,转换成数组,通过递归遍历然后获取

点击查看答案
type StrLen<T extends string, Arr extends string[] = []> = T extends `${infer F}${infer R}` ? StrLen<R, [...Arr, F]> : A['length']
1

// eg type B = StrLen<"javascript"> // 10

# 🚪 实现字符串转换联合类型

实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串

type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
1
2

用infer递归处理

点击查看答案
type StringToUnion<T extends string> = T extends `${infer F}${infer R}` ? F | StringToUnion<R> : never
1

# 🚪 实现是否是给定字符串开头

实现StartsWith<T, U>,接收两个string类型参数,然后判断T是否以U开头,根据结果返回true或false

type a = StartsWith<'abc', 'ac'> // expected to be false
type b = StartsWith<'abc', 'ab'> // expected to be true
type c = StartsWith<'abc', 'abcd'> // expected to be false
1
2
3
点击查看答案

type StartsWith<T extends string, U extends string> = T extends `${U}${infer R}` ? true : false
1
2

拓展 判断是否是指定字符串结尾


type End<T extends string, U extends string> = T extends `${infer R}${U}` ? true : false
1
2

:::

# 🚪 实现去掉字符串空格

type trimed = TrimLeft<'  Hello World  '> // expected to be 'Hello World  
type trimed1 = TrimRright<'  Hello World  '> // expected to be 'Hello World  
type trimed2 = Trim<'  Hello World  '> // expected to be 'Hello World  
1
2
3

把字符串当数组理解,数组是如何提取,字符串就如何提取

  • 递归

  • infer

查看答案
// 一次性解决吧
type S = ' '
type TrimLeft<T extends string> = T extends `${S}${infer R}` ? TrimLeft<R>:T
type TrimRright<T extends string> = T extends `${infer R}${S}` ? TrimRright<R>:T
type Trim<T extends string> = T extends `${S}${infer R}${S}` ? Trim<R>:T
// 看了答案s = ' '不准确,使用 S = ' ' | '\n' | '\t'
1
2
3
4
5
6

# 🚪 实现删除符合要求的字符串

type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
1

递归!!!注意保存每次删除之后的值

点击查看答案
type DropChar<T extends string, S extends string = ''> = T extends `${infer L}${S}${infer R}`
  ? DropChar<`${L}${R}`, S> : T
1
2

# Ⓜ️ 实现驼峰命名转换短横线命名

type FooBarBaz = KebabCase<"FooBarBaz">;
const foobarbaz: FooBarBaz = "foo-bar-baz";

type DoNothing = KebabCase<"do-nothing">;
const doNothing: DoNothing = "do-nothing";
type D = Uncapitalize<"FooBarBaz">
1
2
3
4
5
6

用infer递归处理

点击查看答案
// 第一步写出基本形式
type KebabCase<S extends string> = S extends `${infer S1}${infer S2}` ? `${Uncapitalize<S1>}${Uncapitalize<S2>}`:S
// 判断 S2 开头是否大写,如果是大写,转换成小写,同时在前面添加短横线,
// 递归 infer 提取的 S2
type KebabCase<S extends string> = S extends `${infer S1}${infer S2}` ? (
  S2 extends `${Uncapitalize<S2>}`?  `${Uncapitalize<S1>}${KebabCase<S2>}`: `${Uncapitalize<S1>}-${KebabCase<S2>}`
):S

1
2
3
4
5
6
7
8

# Ⓜ️ 现判断一个字符串中字符是否重复

type S1 = CheckRepeatedChars<'abc'>  // false
type S2 = CheckRepeatedChars<'aba'> // true
1
2

可以参考数组判断重复,借助辅助函数判断是否重复

点击查看答案
// 辅助函数 判断指定字符串是否重复
type IsRepeated<T extends string, U extends string, M extends any[] = []> =
  T extends `${infer L}${infer R}` ?
  U extends L ? IsRepeated<R, U, [...M, L]> : IsRepeated<R, U, M>
  : M['length'] extends 1 ? false : true

// 结果
type CheckRepeatedChars<T extends string, O extends string = T> = 
T extends `${infer L}${infer R}` 
? IsRepeated<O, L> extends true ? true : CheckRepeatedChars<R, O> : false

1
2
3
4
5
6
7
8
9
10
11

# Ⓜ️ 实现 单词首字母大写

实现CapitalWords,它将字符串中每个单词的第一个字母转换为大写字母,其余部分保持原样。

type H1 = CapitalizeWords<"A"> // => 'A'
type H2 = CapitalizeWords<"AB">// => 'Ab'
type H3 = CapitalizeWords<"ABC"> // => 'Abc'
type H4 = CapitalizeWords<" ABC"> // => " Abc"
type H5 = CapitalizeWords<" ABC "> //  => ' Abc '
type H6 = CapitalizeWords<" ABC aDmin"> // => ' Abc Admin '
type H7 = CapitalizeWords<"Hello world hElLO WORlD"> // => Hello World Hello World
1
2
3
4
5
6
7

分析

  • 本题解法同capitalizewords (opens new window)
  • 使用虚拟头节点,将 Head 指向上一次遍历的字符串
点击查看答案
type Line = "_" | "-" | "__"

type CamelCase<T extends string, Head extends string = "", W extends string = "",> = T extends `${infer L}${infer R}`
  ? L extends Line ? CamelCase<R, L, W> : Head extends Line ? CamelCase<R, L, `${W}${Uppercase<L>}`> : CamelCase<R, L, `${W}${Lowercase<L>}`> : W extends `${infer L}${infer R} ` ? `${Lowercase<L>}${R} ` : W

1
2
3
4
5

点击查看本地解答地址 (opens new window)

# 🍅 实现 CamelCase

type camelCase1 = CamelCase<'hello_world_with_types'> // expected to be 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'> // expected to be same as previous one
1
2

分析

  • 隐含条件,不只是将第一个单词大写,还必须将后面相连的字母小写!!!

  • 本题难度在于如果只有一个字符串情况下第一个首字母大写,如 A = >A,a=A,AB = >Ab,ab=>Ab,aB=>Ab

  • 如果直接判断话容易错误,该题有点像链表遍历,因此采用虚拟头节点,不管有没有内容,头节点默认为 ""[空字符串]

点击查看答案
// 空字符串情况
type Space = "" | " " | "\n" | "\t"

// Head 为虚拟头节点,默认为 ""
// 不仅要判断头节点为空!还要判断下一个节点是否为空
type CapitalizeWords<S extends string, Head extends string = "", W extends string = ""> =
  S extends `${infer L}${infer R}` ? Head extends Space ? L extends Space ?
  CapitalizeWords<Lowercase<R>, L, `${W}`> :
  CapitalizeWords<Lowercase<R>, L, `${W}${Uppercase<L>}`> : CapitalizeWords<Lowercase<R>, L, `${W}${L}`> : W

1
2
3
4
5
6
7
8
9
10

点击查看本地解答地址 (opens new window)

# 🍅 实现 LengthOfString

这个问题不同于上面 实现获取字符串长度

type T0 = LengthOfString<"foo"> // 3
1

问题不难!但它们都受到TypeScript的递归限制的限制,这是这个问题的主要约束

LengthOfString

本题参考Maxinum

# 函数系列

# 🚪 实现获取函数返回类型

不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 泛型。 example:

const fn = (v: boolean) => {
    if (v)
        return 1
    else
        return 2
}

type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
1
2
3
4
5
6
7
8

提示

ts中函数表示方式,可以使用关键词infer提取函数返回类型

点击查看答案
type MyReturnType<T> = T extends (...args: any) => infer R ? R : never
1

# Ⓜ️ 实现函数参数反转

本题可以参考数组反转

type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>
// (arg0: boolean, arg1: number, arg2: string) => void
1
2
点击查看
// 提供一个反转数组类型
type Reverse<T extends any[]> = T extends [...infer F, infer R] ? [R, ...Reverse<F>] : T

// 参数反转
type FlipArguments<T extends (...args: any[]) => any> =
  T extends (...args: infer Args) => infer R ?
  (...args: Reverse<Args>) => R : never
1
2
3
4
5
6
7

# 🍅 实现参数追加

type Fn = (a: number, b: string) => number

type Result = AppendArgument<Fn, boolean> 
// expected be (a: number, b: string, x: boolean) => number
1
2
3
4

函数表现形式以及参数和返回类型提取

# 🍅 实现柯里化

const add = (a: number, b: number) => a + b
const three = add(1, 2)

const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)
1
2
3
4
5

柯理华本质是看函数有几个参数,多少个参数返回多少个函数,使用递归

点击查看答案
declare function Currying<F>(fn: F): Curried<F>

// 原函数返回
type Curried<F> = F extends (...args: infer Args) => infer R ?
  (...arg: Args) => R 
  : never


 // 判断函数参数个数,使用递归 【答案】
type Curried<F> = F extends (...args: infer Args) => infer R ?
  Args extends [infer First, ...infer Other] ? (arg: First) => Curried<(...args: Other) => R> : R
  : never


1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Object 系列

# 🚪 实现为一个接口添加新类型

实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。

type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
1
2

构造一个K,V给对象,参考Record实现

点击查看答案
type AppendToObject<T, U extends keyof any, V> = {
  [K in keyof T | U]: K extends keyof T ? T[K] : V
}
1
2
3

# Ⓜ️ 实现连两个对象合并

type foo = {
  name: string;
  age: string;
}
type coo = {
  age: number;
  sex: string
}

type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
1
2
3
4
5
6
7
8
9
10

参考上面给对象添加新属性案例,注意后面对象同名属性必须要覆盖前面对象这个要求

点击查看答案
type Merge<T, U, K extends keyof T = keyof T, J extends keyof U = keyof U> = {
  [key in J | K]: key extends J ? U[key] : (key extends K ? T[key] : never)
}
1
2
3

# Ⓜ️ 实现可配置可选链

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:
type TR = typeof result;
interface Result {
    foo: number
    name: string
    bar: {
        value: string
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

提示

  • 考虑属性是否存在类型中
  • 建造者模式(option 方法类似于建造者模式)
  • 方法表达式
点击查看答案
// 第一步写出最简单形式
type Chainable<T = {}> = {
    option: <K extends string, V>(k: K, v: V) => T,
    get: () => T
};

// 第二部考虑 k 是否存在与 T 中
type Chainable<T = {}> = {
    option: <K extends string, V>(k: K extends keyof T ? never : K, v: V) => T,
    get: () => T
};

// 第三步 如果将 K V 对应属性添加到 T 中 这里使用 Omit<T,K>
type Chainable<T = {}> = {
    option: <K extends string, V>(k: K extends keyof T? never : K, v: V) => Chainable<Omit<T,K>>,
    get: () => T
};

// 第四步,使用 Record 合并属性
type Chainable<T = {}> = {
    option: <K extends string, V>(k: K extends keyof T? never : K, v: V) => Chainable<Omit<T,K>&Record<K,V>>,
    get: () => T
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
点击查看答案
// 第一步写出形式
type AppendArgument<Fn, A> = Fn extends (...args: any[]) => void?(...args: any[]) => void:never

// 第二步使用infer提取参数和返回类型
type AppendArgument<Fn, A> = Fn extends (...args: infer P) => infer T ? (...args: [...P, A]) => T : never
1
2
3
4
5

# Ⓜ️ 实现 Diff

获取两个接口类型中的差值属性。

type Foo = {
  a: string;
  b: number;
}
type Bar = {
  a: string;
  c: boolean
}

type Result1 = Diff<Foo,Bar> // { b: number, c: boolean }
type Result2 = Diff<Bar,Foo> // { b: number, c: boolean }


1
2
3
4
5
6
7
8
9
10
11
12
13
点击查看答案
//
//  解法一、我自己解法 太复杂了😴
// [key in K | J as key extends K & J ? never : key]
// key 属于并集 单不属于 交集
type Diff<T, U, K extends keyof T = keyof T, J extends keyof U = keyof U> = {
  [key in K | J as key extends K & J ? never : key]:
  key extends K ? key extends J ? never : T[key]
  : (key extends J ? (key extends K ? never : U[key]) : never)
}

// 解法二
// [key in keyof (U & T) as key extends keyof (T | U) ? never : key]
// key 属于 T U的并集 单不属于交集
type Diff<T, U> = {
  [key in keyof (U & T) as key extends keyof (T | U) ? never : key]: (U & T)[key]
}

// 解法三,使用 Omit
// 注意了解 Omit 的用法,
type Diff<T, U> = Omit<T & U, keyof (U | T)>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

拓展

// 如果是对象类型, 
//    | 表示 交集,& 表示并集
// 交集
type A = keyof (Foo | Bar) // type A = "a" 
// 并集
type B = keyof (Foo & Bar) // type B = "a" | "b" | "c"


// 对于联合类型 
//  | 表示 并集, & 表示 交集
// 并集
type C = (keyof Foo) | (keyof Bar) // type C = "a" | "b" | "c"
// 交集
type D = (keyof Foo) & (keyof Bar) // type D = "a" 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Ⓜ️ 实现属性替换

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}


type Nodes = NodeA | NodeB | NodeC

// {type: 'A', name: number, flag: string} 
// |{type: 'B', id: number, flag: string} 
// | {type: 'C', name: number, flag: string} 
type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', { name: number, flag: string }>

// 
// {type: 'A', name: never, flag: number} 
// | NodeB 
// | {type: 'C', name: never, flag: number} // would replace name to never
type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', { aa: number }> 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
点击查看答案
// 我的解法
type ReplaceKeys<T, U, S> = T extends T ?
  {
    [key in keyof T]: key extends U ? (
      key extends keyof S ? S[key] : never
    ) : key extends keyof S ? S[key] : T[key]
  }
  : never


// 参考答案
type ReplaceKeys<U, T, Y> = U extends U
  ? {
      [I in keyof U]: I extends T ? (I extends keyof Y ? Y[I] : never) : U[I];
    }
  : never;

// 参考答案
type ReplaceKeys<U, T, Y> = {
  [K in keyof U]: K extends T ? (K extends keyof Y ? Y[K] : never) : U[K]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 🤔 实现移除索引签名

什么是索引签名? (opens new window)

Only some types are allowed for index signature properties: string, number, symbol, template string patterns, and union types consisting only of these.

索引签名属性只允许使用某些类型:字符串、数字、符号、模板字符串模式以及仅由这些类型组成的并集类型。

type Foo = {
  [key: string]: any;
  foo(): void;
}

type A = RemoveIndexSignature<Foo>  // expected { foo(): void }

1
2
3
4
5
6
7

**ts**自带索引签名类型的联合类型**PropertyKey**

点击查看答案
type S = string | symbol | number
type RemoveIndexSignature<T, P = S> = {
  [key in keyof T as P extends key ? never : key extends P? key : never]: T[key]
}

1
2
3
4
5

# 🤔 实现 Entries

从T中,选择一组类型不可分配给U的属性。

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
1
2
3
4
5
6
点击查看答案
type ObjectEntries<T, U extends keyof T = keyof T> = U extends unknown 
  ? [U, T[U] extends (infer F | undefined) 
    ? F
    : T[U]
  ] 
: never
1
2
3
4
5
6

# 🤔 实现 元组递归转换成对象

从T中,选择一组类型不可分配给U的属性。

// 元组转换成对象
type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
type d = TupleToNestedObject<['a', 'b', 'c'], number> // {a: {b: {c:number}}}
1
2
3
4
5
点击查看答案
type TupleToNestedObject<T extends any[], U> =
  T extends [infer L, ...infer R] ? {
    [key in L & string]: TupleToNestedObject<R, U>
  } : U

1
2
3
4
5

# 🤔 实现 将元组转换成枚举

type Arr = ["macOS", "Windows", "Linux"]

type A1 = Enum<Arr, false>
// expect
// type A1 = {
//   readonly macOS: "macOS";
//   readonly Windows: "Windows";
//   readonly Linux: "Linux";
// }
type A2 = Enum<Arr, true>
// expect
// type A2 = {
//   readonly macOS: 0;
//   readonly Windows: 1;
//   readonly Linux: 2;
// }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

数组转换成对象 遍历方式

点击查看答案
type UseIndex<T extends readonly unknown[], k extends string, U extends unknown[] = []> = T extends [infer L, ...infer R] ? [L] extends [k] ? U['length']
  : UseIndex<R, k, [...U, unknown]> : -1

type Enum<T extends readonly any[], B extends boolean = false> = {
  +readonly [K in T[number]]: B extends true ? UseIndex<T, K> : K
}

1
2
3
4
5
6
7

本题解答地址 (opens new window)

# Is 系列

# 🍅 实现 IsNever

type A = IsNever<never>  // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be false
1
2
3
4
5
点击查看答案
type IsNever<T> = [T] extends [never] ? true : false
// 为什么需要括号?       直接 T extends never ? 不行吗

// 测试
type Is_Never<T> = T extends never ? true : false
type A = Is_Never<never>; // => 推算结果为 never 而不是 true 显然不符合题意
// 具体原因请查看下面链接
1
2
3
4
5
6
7

知识点

想要消除分发特性,用 [] 包裹下就行。 TS分发特性 (opens new window)

# 🤔 实现 isUnion

type case1 = IsUnion<string>  // false
type case2 = IsUnion<string|number>  // true
type case3 = IsUnion<[string|number]>  // false
1
2
3

重点!

A extends A 导致A被分发,所以在[B] extends [A] 这里,B 是联合类型,而A 是分发类型,二者如果不等,那么表示A就是联合类型,具体看

点击查看答案
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : false;
1

# ❓ 实现 IsAny

题目描述

Implement a generic IsRequiredKey<T, K> that return whether K are required keys of T .

type A = IsRequiredKey<{ a: number, b?: string },'a'> // true
type B = IsRequiredKey<{ a: number, b?: string },'b'> // false
type C = IsRequiredKey<{ a: number, b?: string },'b' | 'a'> // false
1
2
3
点击查看答案
// 方案一
type IsAny<T> = [{}, T] extends [T, null] ? true : false;
// https://github.com/type-challenges/type-challenges/issues/5794

// 方案
type IsAny<T> = 0 extends (1 & T) ? true : false;
// 本题答案是查看别人的
// https://github.com/type-challenges/type-challenges/issues/232
1
2
3
4
5
6
7
8

技巧

  • any & 任意类型 = any

# 🍅 实现 IsAnyof

在类型系统中实现类似于 Python 中 any 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 true,否则返回 false。如果数组为空,返回 false。

type Sample1 = IsAnyof<[1, '', false, [], {}]> // expected to be true.
type Sample2 = IsAnyof<[0, '', false, [], {}]> // expected to be false.

1
2
3

对象类型 使用 `keyof` 遍历 属性,数组使用 `[number]` 遍历属性

点击查看答案
type FALSE = 0 | '' | false | [] | { [key: string]: never }


type IsAnyof<T extends any[]> = T[number] extends FALSE
  ? false : true;
1
2
3
4
5

拓展

type a1 = {} extends { name: 'a' } ? true : false // false
type b1 = { name: 'a' } extends {} ? true : false // true
type c1 = { [key: string]: never } extends {} ? true : false // true
type d1 = {} extends { [key: string]: never } ? true : false // true
1
2
3
4

# 🍅 实现 IsRequiredKey

题目描述

Implement a generic IsRequiredKey<T, K> that return whether K are required keys of T .

type A = IsRequiredKey<{ a: number, b?: string },'a'> // true
type B = IsRequiredKey<{ a: number, b?: string },'b'> // false
type C = IsRequiredKey<{ a: number, b?: string },'b' | 'a'> // false
1
2
3

可以考虑 `Pick` `Required`

点击查看答案
// 方案一
type IsRequiredKey<T, U extends keyof T = keyof T> = Pick<T, U> extends Pick<Required<T>, U> ? true : false

// 方案二
type IsRequiredKey<T, K extends keyof T> = T[K] extends NonNullable<T[K]> ? true : false
// type NonNullable<T> = T & {};
1
2
3
4
5
6

# 🍅 实现 IsPalindrome

描述

Implement type IsPalindrome<T> to check whether a string or number is palindrome.

For example:

IsPalindrome<'abc'> // false
IsPalindrome<121> // true
1
2

递归

点击查看答案
type IsPalindrome<T extends string | number, K = `${T}`> =
  K extends `${infer L}${infer R}` ?
  R extends '' ? true :
  K extends `${L}${infer S}${L}` ? IsPalindrome<S> : false
  : true
1
2
3
4
5

# 中等系列

# Ⓜ️ 实现 Omit

不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型。

interface Todo {
    title: string
    description: string
    completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
    completed: false,
}
1
2
3
4
5
6
7
8
9
10
11

提示

  • keyof
  • in
  • extends
  • never
点击查看答案
type MyOmit<T, K extends keyof T> = {
    [P in keyof T as P extends K ? never : P]: T[P]
}
1
2
3

# Ⓜ️ 实现元组转换成联合类型

type Arr = ['java', 'javascript', 'typescript', "node", "springboot"]

type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'


1
2
3
4
5

提示

  • 递归
  • infer
点击查看答案

// 解法一,自己做的
type TupleToUnion<T extends any[]> = T extends [infer F, ...infer L] ? F | TupleToUnion<L> : never

// 解法二 ,说实话,根本看不懂!
// 原答案链接 https://github.com/type-challenges/type-challenges/issues/284
type TupleToUnion<T extends any[]> = T[number]
1
2
3
4
5
6
7

# Ⓜ️ 实现 Absolute

实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串

type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
1
2

用字符串和infer处理

点击查看答案
type Absolute<T extends string | number | bigint> = `${T}` extends `-${infer R}` ? R : `${T}`
1

拓展:如何获取绝对值呢?

# 🤔 实现 MinusOne

给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。

type Zero = MinusOne<1> // 0
type FiftyFour = MinusOne<55> // 54
1
2

ts中是无法直接减一的,但是根据题目要求,可以联想数组,数组的长度和最大下标就是差一!

点击查看答案
type MinusOne<T, P extends any[] = []>
  = P['length'] extends T ? P extends [...infer F, any] ? F['length'] : []['length'] : MinusOne<T, [...P, 0]>

1
2
3

# Ⓜ️ 实现 PickByType

挑选出类型

type OnlyBoolean = PickByType<{
  name: string
  count: number
  isReadonly: boolean
  isEnable: boolean
}, boolean> // { isReadonly: boolean; isEnable: boolean; }
1
2
3
4
5
6
点击查看答案
// 解法一
type PickByType<T extends object = {}, U = any> = {
  [K in keyof T as T extends K ? never : T[K] extends U ? K : never]: T[K]
}

// 解法二 是要 pick 注意 {}[keyof T] 的含义
type PickByType<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T]


// 解法三 和 解法一类似
type PickByType<T extends Object, U> = {
  [Key in keyof T as T[Key] extends U ? Key : never]: T[Key]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 🍅 实现 PartialByKeys

实现一个通用的PartialByKeys<T, K>,它接收两个类型参数T和K。

K指定应设置为可选的T的属性集。当没有提供K时,它就和普通的Partial<T>一样使所有属性都是可选的。

interface User {
  name: string
  age: number
  address: string
}

type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
1
2
3
4
5
6
7
点击查看答案
type IO<T extends object = {}> = {
    [k in keyof T]: T[k]
}


type PartialByKeys<T extends object = {}, K = any> =
IO<
    {
        [P in keyof T as P extends K ? P : never]?: T[P]
    }
    &
    {
        [P in keyof T as P extends K ? never : P]: T[P]
    }
>


// 解法二
type PartialByKeys<T extends object = {}, K = any> =
  IO<
    {
      [P in Extract<keyof T, K>]?: T[P]
    }
    &
    {
      [P in Exclude<keyof T, K>]: T[P]
    }
  >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

提示

  • Extract 期望类型
  • Exclude 排除类型
  • 联合类型中& 表示交集, | 表示并集;对象类型中& 表示并集, | 表示交集

# Ⓜ️ 实现 OmitByType

从T中,选择一组类型不可分配给U的属性。

type OmitBoolean = OmitByType<{
  name: string
  count: number
  isReadonly: boolean
  isEnable: boolean
}, boolean> // { name: string; count: number }
1
2
3
4
5
6
点击查看答案
type OmitByType<T, U> = {
  [P in keyof T as T[P] extends U ? never : P]: T[P]
}
1
2
3

# Ⓜ️ Bem 架构

块、元素、修饰符方法论(BEM)是CSS中常用的类命名约定。例如,块组件将表示为btn,依赖于块的元素将表示为btn_price,改变块样式的修饰符将表示为btn-big或btn_price-warning。实现BEM<B,E,M>,根据这三个参数生成字符串并集。其中B是字符串文字,E和M是字符串数组(可以为空)。

type A = BEM<'el', ['button', 'button'], ['primary', 'success']>
// "el__button--primary" | "el__button--success"
1
2
点击查看答案
type IsNever<T> = [T] extends [never] ? true : false
type IsUnion<T> = IsNever<T> extends true ? "" : T
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${IsUnion<`__${E[number]}`>}${IsUnion<`--${M[number]}`>}`
1
2
3

# 🍅 指定返回的联合类型

type result = NumberRange<2, 9> //  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 
1
点击查看答案
// 根据数字生成数组
type AppendArray<R extends number, T extends any[] = []> = T['length'] extends R ? T : AppendArray<R, [...T, T['length']]>

// 数值加一
type AddLength<T extends any[]> = [...T, T['length']]

// 去掉指定前面个数
type Shfit<T extends any[], N extends number, U extends any[] = []> =
 U['length'] extends N ? T : T extends [any, ...infer R] ? Shfit<R, N, [...U, 0]> : T

// 数组转换联合类型
type ArrayToUnion<T extends any[]> = T[number]

// result
type NumberRange<T extends number, R extends number> = ArrayToUnion<Shfit<AddLength<AppendArray<R>>, T>>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 未完待续……

# 困难系列

# 🍅 实现 GetRequired

实现高级util类型GetRequired<T>,该类型保留所有必填字段

type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
1
点击查看答案

// 解法一
// 使得所有key变成必须选类型
type MyRequried<T> = {
  [k in keyof T]-?: T[k]
}

type GetRequired<T> = {
  [key in keyof T as T[key] extends MyRequried<T>[key] ? key : never]: T[key]
}



// 解法二


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

点击查看本地解答地址 (opens new window)

# 🍅 实现 GetRequiredkeys

type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be “foo”
1
2

和上面解法类似,对象类型转换成联合类型

点击查看答案
type MyRequried<T> = {
  [key in keyof T]-?: T[key]
}

type GetRequired<T> = {
  [key in keyof T as T[key] extends MyRequried<T>[key] ? key : never]: key
}
type RequiredKeys<T> = GetRequired<T>[keyof GetRequired<T>]

1
2
3
4
5
6
7
8
9

点击查看本地解答地址 (opens new window)

拓展

  • Array类型转换成联合类型

# 🍅 实现 GetOptional

保留可选类型的key,转换成联合类型

type I = GetOptional<{ foo: number, bar?: string }> // expected to be { bar?: string }
1

和上面解法类似,只不过改变了一下判断依据

点击查看答案
type MyRequired<T> = {
  [key in keyof T]-?: T[key]
}


type GetOptional<T> = {
  [key in keyof T as T[key] extends MyRequired<T>[key] ? never : key]: T[key]
}


1
2
3
4
5
6
7
8
9
10

点击查看本地解答地址 (opens new window)

# 🍅 实现 GetOptionalKeys

保留可选类型的key,转换成联合类型

type I = GetOptional<{ foo: number, bar?: string }> // expected to be "bar"
1
点击查看答案


type MyRequired<T> = {
  [key in keyof T]-?: T[key]
}


type GetOptional<T> = {
  [key in keyof T as T[key] extends MyRequired<T>[key] ? never : key]: key
}

// 将得到的可选类型转换成必须类型,去除 undefined 
type OptionalKeys<T> = MyRequired<GetOptional<T>>[keyof MyRequired<GetOptional<T>>]
1
2
3
4
5
6
7
8
9
10
11
12
13

点击查看本地解答地址 (opens new window)

# 🍅 实现 SimpleVue

实现类似Vue的类型支持的简化版本。

通过提供一个函数SimpleVue(类似于Vue.extend或defineComponent),它应该正确地推断出 computed 和 methods 内部的this类型。

在此挑战中,我们假设SimpleVue接受只带有data,computed和methods字段的Object作为其唯一的参数,

  • data是一个简单的函数,它返回一个提供上下文this的对象,但是你无法在data中获取其他的计算属性或方法。
  • computed是将this作为上下文的函数的对象,进行一些计算并返回结果。在上下文中应暴露计算出的值而不是函数。
  • methods是函数的对象,其上下文也为this。函数中可以访问data,computed以及其他methods中的暴露的字段。 computed与methods的不同之处在于methods在上下文中按原样暴露为函数。

SimpleVue的返回值类型可以是任意的。

const instance = SimpleVue({
  data() {
    return {
      firstname: 'Type',
      lastname: 'Challenges',
      amount: 10,
    }
  },
  computed: {
    fullname() {
      return this.firstname + ' ' + this.lastname
    }
  },
  methods: {
    hi() {
      alert(this.fullname.toLowerCase())
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
点击查看答案
type GetComputed<C> = C extends Record<string, (...args: any[]) => any> 
  ? { [S in keyof C]: ReturnType<C[S]> } 
  : never

declare function SimpleVue<D, C, M>(
  options: {
    data: () => D,
    computed: C,
    methods: M,
  } & ThisType<D & M & GetComputed<C>>
): any
1
2
3
4
5
6
7
8
9
10
11

# 🍵 实现将字符串转换成数字

点击查看
type ToNumber<S extends string> = S extends `${infer N extends number}` ? N : never;
1

这也是难题?

# 🤔 实现两数之和

给定一个整数数组 nums 和一个目标整数 target, 如果 nums 数组中存在两个元素的和等于 target 返回 true, 否则返回 false

理清思路,排序,左右指针

点击查看
// 方法一使用函数实现 
function TowSum(nums: number[], target: number): boolean {
  if (nums['length'] < 2) {
    return false
  }
  nums.sort((a, b) => a - b > 0 ? 1 : -1)
  let left = 0, right = nums['length'] - 1
  let sum: number
  while (left < right) {
    sum = nums[left] + nums[right]
    if (sum === target) {
      return true
    } else if (sum > target) {
      right--;
    } else if (sum < target) {
      left++;
    }
  }
  return false
}


// 方法二使用类型实现
// 暂时搁置...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

我的解答地址 (opens new window)

# 📅 实现日期验证

要求实现一个类型验证月份和日期

ValidDate<'0102'> // true
ValidDate<'0131'> // true
ValidDate<'1231'> // true
ValidDate<'0229'> // false
ValidDate<'0100'> // false
ValidDate<'0132'> // false
ValidDate<'1301'> // false
1
2
3
4
5
6
7

本题可以采用枚举实现,知道哪些月份是月大,哪些是月小,以及二月份是一个特殊月份!

点击查看答案
// 数字的范围
type Num = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

type D = 0 | 1 | 2
// 月小
type MinMM = `0${4 | 6 | 7}` | `11`
// 月大
type MaxMM = Exclude<`0${Num}` | `1${D}`, MinMM | `02`>

type Day = `${D}${Num}` | `3${Exclude<D, 2>}`

type ValidDate<T extends string> = T extends `${MaxMM}${Day}` | `${MinMM}${Exclude<Day, `31`>}` | `02${Exclude<Day, `29` | `30` | `31`>}` ? true : false


1
2
3
4
5
6
7
8
9
10
11
12
13
14

本题我的解答地址 (opens new window)

# 🍰 实现 Get

实现一个Get类型,获取对象属性

type Data = {
  foo: {
    bar: {
      value: 'foobar',
      count: 6,
    },
    included: true,
  },
  hello: 'world'
}
  
type A = Get<Data, 'hello'> // 'world'
type B = Get<Data, 'foo.bar.count'> // 6
type C = Get<Data, 'foo.bar'> // { value: 'foobar', count: 6 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

判断 属性中是否含有`.`是切入点

点击查看答案
type Get<T, K extends string> = K extends `${infer L}.${infer R}` ? L extends keyof T ? Get<T[L], R> : never : K extends keyof T ? T[K] : never
1

本题我的解答地址 (opens new window)

# 🍅 实现 Maximum

题目描述

Implement the type Maximum, which takes an input type T, and returns the maximum value in T.

If T is an empty array, it returns never. Negative numbers are not considered.

Maximum<[]> // never
Maximum<[0, 2, 1]> // 2
Maximum<[1, 20, 200, 150]> // 200
1
2
3

难题单元化,多使用辅助类型

点击查看答案

// 设置一个默认值为0
// 当 C['length'] === A | B 时退出递归
// 判断 C['length'] === A or  C['length'] === B 先 === 的为较小值
// 不过这种方式当 A B 值为 负数时 该方案错误!
// 因此分为四种情况
// 1 A>= 0 B>=0     ===========> 比较 length ,谁的 length 大 返回谁
// 2 A>0 B<0        ===========> 直接返回 A
// 3 A<0 B>0        ===========> 直接返回 B
// 4 A<0 B<0        ========== > 谁的length小,值越大
// isAllNegative 表示两个数是否为负数
// A B 表示绝对值
// A1 表示 A 对应 如果A 是正数,L 和也是正数 如果 A = 1 ,L = 1 =>1; A = 1,A1 = -1 => -1
// B1 同 A1与 A 对应关系
type Compare<A extends number, B extends number, isAllNegative extends boolean = false, A1 extends number = A, B1 extends number = B, C extends any[] = []> =
  C['length'] extends A | B ?
  C['length'] extends A ? isAllNegative extends true ? A1 : B1 : isAllNegative extends true ? B1 : A1
  : Compare<A, B, isAllNegative, A1, B1, [...C, unknown]>


// 辅助类型
// 判断是否是负数
type IsNegative<T extends number> = `${T}` extends `-${infer A extends number}` ? true : false

// 获取绝对值
type GetNegative<T extends number> = `${T}` extends `-${infer A extends number}` ? A : T


// 两个负数数字越小值越大!
// 如果是一个正数和一个负数 就没必要对比了直接返回正数
type Max<A extends number, B extends number> =
  IsNegative<A> extends true
  ? IsNegative<B> extends true
  // A < 0 , B < 0
  ? Compare<GetNegative<A>, GetNegative<B>, true, A, B> : B
  : IsNegative<B> extends true ? A : Compare<A, B>




type Maximum<T extends number[], M extends number = T[0], First extends boolean = true> =
  T['length'] extends 0 ? (
    First extends true ? never : M
  ) :
  T extends [infer A extends number, ...infer B extends number[]] ?
  Maximum<B, Max<A, M>, false> : M




// test
type A1 = Max<10, 2> // 10
type A2 = Max<2, 10> // 10
type B1 = Max<0, 1> // 1
type B2 = Max<0, -1> // 0
type C1 = Max<-1, 1> // 1
type C2 = Max<1, -1> // 1
type D1 = Max<-2, -1> // -1
type D2 = Max<-1, -2> // -1
type E1 = Max<-100, -200> // -100
type E2 = Max<-200, -100> // -100

// test
type arr0 = Maximum<[]> // never
type arr1 = Maximum<[-1, 20, -200, -150]> // 20
type arr2 = Maximum<[-1, -20, -200, -150]> // -1
type arr3 = Maximum<[1, 20, 200, -150]> // 200
type arr4 = Maximum<[0, -1]> // 0
type arr5 = Maximum<[-1, 1]> // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

本题是我多考虑了,官方没要求负数情况

如果要求是负数该如何实现呢?

优化后结果

点击查看答案

// 辅助类型
type NumToString<T extends number> = `${T}`
// 获取字符串长度
type GetStringLen<T extends string, U extends any[] = []> = T extends `${infer L}${infer R}` ? GetStringLen<R, [...U, L]> : U['length']
// 返回较小数字
type GetMinNumber<A extends number, B extends number, L extends any[] = []> = L['length'] extends A | B ? L['length'] extends A ? A : B : GetMinNumber<A, B, [...L, -1]>
// number is equal
type NumIsEqual<A extends number, B extends number> = A extends B ? true : false
// strlen is equal
type StrLenIsEqual<A extends string, B extends string> = GetStringLen<A> extends GetStringLen<B> ? true : false
// 返回较大值 A B 属于 0-9 A1 B1 为原始值
type GetMaxNumber<A extends number, B extends number, A1 extends number = A, B1 extends number = B, isAllNegative extends boolean = false,>
  = A extends GetMinNumber<A, B> ? isAllNegative extends true ? A1 : B1 : isAllNegative extends true ? B1 : A1

// 判断是否是负数
type IsNegative<T extends number> = `${T}` extends `-${infer A extends number}` ? true : false

// 获取绝对值
type GetNegative<T extends number> = `${T}` extends `-${infer A extends number}` ? A : T



type Compare<
  A extends number,
  B extends number,
  isAllNegative extends boolean = false,
  A1 extends number = A,
  B1 extends number = B,
  S1 extends string = NumToString<A>,
  S2 extends string = NumToString<B>
> =
A1 extends B1 ? A1:
  StrLenIsEqual<S1, S2> extends true ?
  (
    S1 extends `${infer L1 extends number}${infer R1}` 
    ? S2 extends `${infer L2 extends number}${infer R2}`
    // 比较单个字符串对应数字情况
    ? NumIsEqual<L1, L2> extends true ?
    // 相等递归下一次
    Compare<A, B, isAllNegative, A1, B1, R1, R2> :
    // 不相等话比较数字大小
    GetMaxNumber<L1, L2, A1, B1, isAllNegative>
    // 对比到此处说明 A1 === B1 所以任意返回一个值就行了
    : never : never
  ) 
  : 
  (
    // 转换成字符串后长度不相等
    GetStringLen<S1> extends GetMinNumber<GetStringLen<S1>, GetStringLen<S2>> ?
    // S1 长度小于 S2
    isAllNegative extends false ? B1 : A1 :
    // S1长度大于 S2
    isAllNegative extends false ? A1 : B1
  )


  // 分为四种情况
// 1 A>= 0 B>=0     ===========> 比较 length ,谁的 length 大 返回谁
// 2 A>0 B<0        ===========> 直接返回 A
// 3 A<0 B>0        ===========> 直接返回 B
// 4 A<0 B<0        ========== > 谁的length小,值越大
type Max<A extends number, B extends number> =
IsNegative<A> extends true
? IsNegative<B> extends true
// A < 0 , B < 0
? Compare<GetNegative<A>, GetNegative<B>, true, A, B> : B
: IsNegative<B> extends true ? A : Compare<A, B>



// 答案
type Maximum<T extends number[], M extends number = T[0], First extends boolean = true> =
T['length'] extends 0 ? (
  First extends true ? never : M
) :
T extends [infer A extends number, ...infer B extends number[]] ?
Maximum<B, Max<A, M>, false> : M


// test
type N1 = NumToString<100>
type N2 = NumToString<20>
type S1 = GetStringLen<N1>

// test  GetMaxNumber
type G1 = GetMaxNumber<1, 2, 100, 200> // 200
type G2 = GetMaxNumber<1, 2, 100, 200>  // 200
type G3 = GetMaxNumber<2, 1, -200, -100, true> // -100
type G4 = GetMaxNumber<1, 2, -100, -200, true> // -100
type G5 = GetMaxNumber<1, 1, 10, 10, false> // 10
type G6 = GetMaxNumber<1, 1, 10, 10, false> // 


//test
type A1 = Max<30000000, 20000000> // 30000000
type A2 = Max<20000000, 30000000> // 30000000
type B1 = Max<0, 100000000> // 100000000
type B2 = Max<100000000, 0> // 100000000
type C1 = Max<-10000000000, -20000000000> // -10000000000
type C2 = Max<-20000000000, -10000000000> // -10000000000
type D1 = Max<-20000000, -10000000000000> // -20000000
type D2 = Max<-10000000000000, -20000000> // -20000000
type E1 = Max<-30000000, -30000000> // -20000000
type E2 = Max<-30000000, -30000000> // -20000000

// test
type arr0 = Maximum<[]> // never
type arr1 = Maximum<[-100000, -2000000, -2000000, -150000000]> // -100000
type arr2 = Maximum<[-10, -200000, -2000, -1500000000]> // -10
type arr3 = Maximum<[100000000, 20000, 20000, -150000]> // 100000000
type arr4 = Maximum<[0, -1]> // 0
type arr5 = Maximum<[-1, 1]> // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

总结一下

  1. 均为正数
    • 转换成字符串比较,谁的长度大,值就越大
    • 同长度,将各个字符对应值,从左到右逐一比较,同位置字符对应数字越大,对应值越大,可以退出比较了
  2. 一正一负 直接返回正数
  3. 均为负数
    • 转换成字符串,谁的长度小,值就越大
    • 同长度,将各个字符对应值,从左到右逐一比较,同位置字符对应数字越小,对应值越大,可以退出比较了

为什么需要转换成字符串?

当数字值越大时,递归深度就越大,出现栈溢出。

转换成字符串有什么好处?

只比较长度,不会存在深度递归,同长度情况下,可以从左到右逐一比对,由于比较的是单个数字,这种情况下最大也是9,不会存在深度递归问题

本题解答地址 (opens new window)

// 未完待续……

# 地狱

# 🚀 实现 Equal

是不是很奇怪?一个简单 Equal 竟然放在地狱级别中,其实坑巨多

先看看最简单想到的方案

type MyEqual<A, B> = A extends B ? true : false

// test MyEqula
type M1 = MyEqual<1, 2> // false
type M2 = MyEqual<2, 1> // false
type M3 = MyEqual<1, 1> // true
type M4 = MyEqual<"a", "b"> // false
type M5 = MyEqual<"b", "a"> // false
type M6 = MyEqual<"a", "a"> // false
type M7 = MyEqual<any, 1> // boolean 
type M8 = MyEqual<1, any> // true
type M9 = MyEqual<1, never> // false
type M10 = MyEqual<never, 1> // never
type M11 = MyEqual<never, any> // never
type M12 = MyEqual<any, never> // boolean
type M13 = MyEqual<never, never> // never
type M14 = MyEqual<{}, {}> // true
type M15 = MyEqual<[], []> // true
type M16 = MyEqual<{}, []> // false
type M17 = MyEqual<[], {}> // false
type M18 = MyEqual<[], any> // true
type M18_1 = MyEqual<any, []> // boolean
type M19 = MyEqual<any, {}> // boolean
type M20 = MyEqual<{}, any> // true
type M21 = MyEqual<any, any> // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

哎这不对吧?为什么有的出现 never 有的出现 boolean?

这就涉及到 ts 高级特性分发

改进方案


type MyEqual<A, B> = [A] extends [B] ? true   : false
// ... 其他测试通过方案省略,下面演示不过的案例
type M7 = MyEqual<any, 1> // true           ==============> 不符合期望 false 
type M10 = MyEqual<never, 1> // true        =============> 不符合期望 false
type M11 = MyEqual<never, any> // true      =============> 不符合期望 false
type M18_1 = MyEqual<any, []>  // true     =============> 不符合期望 false
type M19 = MyEqual<any, {}>   // true      =============> 不符合期望 false
type M20 = MyEqual<{name:string}, {readonly name:string}>   // true      =============> 不符合期望 false
type M21 = MyEqual<{readonly name:string}, { name:string}>   // true      =============> 不符合期望 false

1
2
3
4
5
6
7
8
9
10
11

为了合理再改进

type MyEqual<A, B> = [A] extends [B] ? ([A] extends [B] ? true:false)   : false
1

经过测试答案和上面一致,貌似这种方式也是错误的,有没有正确答案?

ts 官方是这样实现的

type MyEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
1

为什么是这样,俺也不知道……,确实想不到, 暂时记着吧 😂

# 🚀 实现 slice

是不是觉得 slice 很简单,为什么是地狱级别?

题目描述

Implement the JavaScript Array.slice function in the type system. Slice<Arr, Start, End> takes the three argument. The output should be a subarray of Arr from index Start to End. Indexes with negative numbers should be counted from reversely.

type Slice<
  T extends any[],
  Start extends number = 0,
  End extends number = Arr['length'],
> =
  // 处理入参后交由只处理合法数据的 DealSlice 处理
  DealSlice<
    T,
    ConvertIndex<Start, T['length']>,
    ConvertIndex<End, T['length']>
  >;

// 本题核心实现
type DealSlice<
  T extends any[],
  Start extends number = 0,
  End extends number = Arr['length'],
  // 记录当前位置
  Cur extends number[] = [],
  // 标志位
  flag = false,
> = GreaterThan<Start, End> extends false // 开始位置是否大于结束位置
  ? T extends [infer F, ...infer R]
    ? // 遍历元组,如果当前达到了起始位置
      Cur['length'] extends Start
      ? // 同时也达到了结束位置,此时对应 start === end 的场景,返回空元组
        Cur['length'] extends End
        ? []
        : // 否则,将 元素加入新元组中,并在递归中将 flag 改为 true,表示在区间内
          [F, ...DealSlice<R, Start, End, [...Cur, 1], true>]
      : // 达到了结束位置
      Cur['length'] extends End
      ? // 此时后续元素也不关心,直接返回空元组
        []
      : // 不是起始也不是结束的场景,此时根据 flag 的情况,决定是否要把当前元素放入元组中
      // 并将 flag 保留当前值继续后续遍历
      flag extends true
      ? [F, ...DealSlice<R, Start, End, [...Cur, 1], flag>]
      : DealSlice<R, Start, End, [...Cur, 1], flag>
    : []
  : []; // 开始位置大于结束位置,直接返回 []

// [4425-实现比较](/medium/4425-实现比较.md)
type GreaterThan<T extends number, U extends number, Arr extends any[] = []> =
  // 先达到 T,则 T 小
  T extends Arr['length']
    ? false
    : // 先达到 U
    U extends Arr['length']
    ? // 则 T 大
      true
    : // 都没到,膨胀元组
      GreaterThan<T, U, [...Arr, 1]>;

// 构建长度为 Length 的元组
type ArrWithLength<Length extends number, Arr extends any[] = []> =
  // 元组长度等于目标长度时
  Arr['length'] extends Length
    ? // 返回元组
      Arr
    : // 否则,向 Arr 中增加一个元素,并递归处理新数组
      ArrWithLength<Length, [...Arr, any]>;

// 减法实现: [进阶-计数-加减乘除](/summary/进阶-计数-加减乘除.md)
type Substract<A extends number, B extends number> = ArrWithLength<A> extends [
  ...ArrWithLength<B>,
  ...infer R,
]
  ? R['length']
  : never;

// 处理负数入参
// 如果是负数 -n,返回 length - n, length - n > 0, 那么返回 0
// 如果是正数 n,返回 n
type ConvertIndex<
  Index extends number,
  Length extends number,
> = `${Index}` extends `-${infer F extends number}`
  ? GreaterThan<Length, F> extends true
    ? Substract<Length, F>
    : 0
  : Index;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 🚀 获取只读字段

题目描述

实现泛型GetReadonlyKeys<T>,GetReadonlyKeys<T>返回由对象 T 所有只读属性的键组成的联合类型。

interface Todo {
  readonly title: string
  readonly description: string
  completed: boolean
}

type Keys = GetReadonlyKeys<Todo> // expected to be "title" | "description"
1
2
3
4
5
6
7

如何获取 `readonly` 是个难度,可以从 `pick` 和 `Equal` 入手,主要难的想到使用 `Equal`以及 `Equal`实现

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

type GetReadonlyKeys<
  T,
  U extends Readonly<T> = Readonly<T>,
  K extends keyof T = keyof T
> = K extends keyof T ? Equal<Pick<T, K>, Pick<U, K>> extends true ? K : never : never;
1
2
3
4
5
6
7

# 🚀 实现柯理化2

嗯……,柯里化不是实现了,为什么还会出现在这里?

困难级别中是固定参数个数,现在是动态参数了。

题目描述 在前端的日常开发中,柯里化函数参数个数动态化却是非常常见的

const func = (a: number, b: number, c: number) => {
  return a + b + c
}

const bindFunc = func(null, 1, 2)

const result = bindFunc(3) // result: 6
1
2
3
4
5
6
7

因此,在 柯里化 的基础上,我们更需要的是 动态参数化的柯里化函数

const add = (a: number, b: number, c: number) => a + b + c
const three = add(1, 1, 1) 

const curriedAdd = DynamicParamsCurrying(add)
const six = curriedAdd(1, 2, 3)
const seven = curriedAdd(1, 2)(4)
const eight = curriedAdd(2)(3)(4)
1
2
3
4
5
6
7

要求

传递给 DynamicParamsCurrying 的函数可能有多个参数,您需要实现它的类型。

在此挑战中,curriedAdd函数每次可接受最少一个参数,但是所有参数个数总和及类型与原函数相同。分配完所有参数后,它应返回其结果。

declare function DynamicParamsCurrying<A extends any[], R>(
  fn: (...args: A) => R,
): A extends []
  ? R
  : // 返回一个函数,其入参 P 通过在运行时进行推导
    <P extends any[]>(
      ...args: P
    ) => A extends [...P, ...infer Rest]
      ? Rest extends []
        ? R
        : // 对剩余的元素,递归处理,借助了 typeof, ReturnType 的内置类型
          ReturnType<typeof DynamicParamsCurrying<Rest, R>>
      : never;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 🚀 实现 QueryStringParser

题目描述

You're required to implement a type-level parser to parse URL query string into a object literal type.

Some detailed requirements:

  • Value of a key in query string can be ignored but still be parsed to true. For example, 'key' is without value, so the parser result is { key: true }.
  • Duplicated keys must be merged into one. If there are different values with the same key, values must be merged into a tuple type.
  • When a key has only one value, that value can't be wrapped into a tuple type.
  • If values with the same key appear more than once, it must be treated as once. For example, key=value&key=value must be treated as key=value only.

本题是解析 query,要求是几个点:

  1. k1 需要被解释为 { K1: true }
  2. 多次出现的且值不同,需要被改成元组类型,也就是 K1=1&K1=2&K1=3,被解释为 { K1: [1, 2, 3] }
  3. 多次出现的相同值,不会被处理。

分析

  • 如何提取 K=V => {K:V}

  • 如何提取 &K =>{K:true}

  • 如何将 K=V1&K=V2 =>{K:[V1,V2]}

  • 如何将提取结果保存

type SingleProp<T extends string> =
  T extends `${infer K}=${infer V}`?
  {
    [P in  K]: V
  } 
  : 
  {
    [P in T as P extends '' ? never : P]: true
  }

  //  test
  type S1=SingleProp<"K=V"> // {K:V}
  type S2=SingleProp<"K"> // {K:true}

// 判断 T 中是否包含 U
type Includes<T, U> = T extends [infer F, ...infer R]
  ? F extends U
    ? true
    : Includes<R, U>
  : false;

// 合并同时存在的属性值
type MergeSingle<U1, U2> =
  // 如果 是元组
  U1 extends any[]
    ? // 判断是否包含 U2
      Includes<U1, U2> extends true
      ? // 包含,则不纳入
        U1
      : // 否则,纳入
        [...U1, U2]
    : // 不是元组,判断当前值是否相同
    U1 extends U2
    ? // 相同,则不组成元组,直接返回 U1
      U1
    : // 否则,组成元组
      [U1, U2];


      // 完整的合并操作
type Merge<T1, T2> = {
  // 遍历所有属性
  [P in keyof T1 | keyof T2]: P extends keyof T1
    ? P extends keyof T2
      ? // 如果是两者都有的属性,调用上述的 MergeSingle
        MergeSingle<T1[P], T2[P]>
      : // 否则返回前者
        T1[P]
    : P extends keyof T2
    ? // 否则返回后者
      T2[P]
    : never;
};

type ParseQueryString<T extends string, Res = {}> =
  // 根据 & 匹配前后字符
  T extends `${infer F}&${infer R}`
    ? ParseQueryString<
        R,
        // 把 F 解析后的结果合并到 Res 中
        Merge<Res, SingleProp<F>>
      >
    : // 匹配结束,处理 T 之后合并到 Res 中并返回便是答案
      Merge<Res, SingleProp<T>>;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

拓展

相同key多次出现相同值,不合并,只保留第一次的value

# 🚀 实现 JSON Parse ❓

You're required to implement a type-level partly parser to parse JSON string into a object literal type.

Requirements:

  • Numbers and Unicode escape (\uxxxx) in JSON can be ignored. You needn't to parse them.

答案来源 (opens new window) 看不懂

//My answer is only for the test cases
type Parse<T extends string> = Eval<T> extends [infer V, infer U] ? V : never

type Eval<T>
  = T extends `${' '|'\n'}${infer U}` ? Eval<U>
  : T extends `true${infer U}` ? [true, U]
  : T extends `false${infer U}` ? [false, U]
  : T extends `null${infer U}` ? [null, U]
  : T extends `"${infer U}` ? EvalString<U>
  : T extends `${'['}${infer U}` ? EvalArray<U>
  : T extends `${'{'}${infer U}` ? EvalObject<U>
  : false

type Escapes = {r:'\r', n:'\n', b:'\b', f:'\f'}

type EvalString<T, S extends string = ''>
  = T extends `"${infer U}` ? [S, U]
  : (T extends `\\${infer C}${infer U}` ? C extends keyof Escapes ? [C, U] : false : false) extends [infer C, infer U] 
    ? EvalString<U, `${S}${C extends keyof Escapes ? Escapes[C] : never}`>
  : T extends `${infer C}${infer U}` ? EvalString<U, `${S}${C}`>
  : false

type EvalArray<T, A extends any[] = []> 
  = T extends `${' '|'\n'}${infer U}` ? EvalArray<U, A>
  : T extends `]${infer U}` ? [A, U]
  : T extends `,${infer U}` ? EvalArray<U, A>
  : Eval<T> extends [infer V, infer U] ? EvalArray<U, [...A, V]> 
  : false

type EvalObject<T, K extends string = '', O = {}> 
  = T extends `${' '|'\n'}${infer U}` ? EvalObject<U, K, O>
  : T extends `}${infer U}` ? [O, U]
  : T extends `,${infer U}` ? EvalObject<U, K, O>
  : T extends `"${infer U}` ? Eval<`"${U}`> extends [`${infer KK}`, infer UU] ? EvalObject<UU, KK, O> : false  
  : T extends `:${infer U}` ? Eval<U> extends [infer V, infer UU] ? EvalObject<UU, '', Merge<{[P in K]: V} & O>> : false
  : false

type Merge<T> = {[P in keyof T]: T[P]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

// 未完待续……

# 总结

// 未完待续……

# 相关链接

  • ts训练营 (opens new window)

  • maxiaobo (opens new window)

编辑 (opens new window)
#TypeScript#challenge
上次更新: 2025-06-05, 08:31:31
TypeScript笔记

← TypeScript笔记

最近更新
01
vscode配置c++环境
05-04
02
LeetCode刷题工具之本地对拍
04-04
03
sublime对应语言模板使用技巧
02-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 wuxin0011 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式