前端学习记录
  • 前言及目录
  • 前端基础
    • HTML
    • CSS
      • CSS学习之布局
    • JavaScript
      • 跟着月影学JavaScript
      • JavaScript之对象、原型链及继承
      • JavaScript中的类
      • onclick与addEventListener区别
      • JS手撕题
    • HTTP与浏览器
      • HTTP实用指南
      • Web开发的安全之旅
    • 通用知识
      • 前端必须知道的开发调试知识
      • 前端设计模式应用
      • Web 标准与前端开发
  • 数据结构及算法
    • 数据结构
      • 1、线性表(List)
      • 2、堆栈(Stack)
      • 3、队列(Queue)
      • 4、二叉树(Binary Tree)
      • 5、二叉搜索树与平衡二叉树(BST & AVL)
      • 6、堆(Stack)& 哈夫曼树 & 并查集
      • 7、图(Graph)
        • 图论——解决最小生成树问题(Kruskal算法&Prim算法)
      • 8、排序(sort)
      • 9、散列表(hash)
      • 数据结构习题
        • 第一周:最大子列和算法、二分查找
        • 第二周:线性结构
        • 第三周:栽树(二叉树等)
        • 第四周:二叉搜索树&二叉平衡树
        • 第五周:堆&哈夫曼树&并查集
        • 第六周:图(上)连通集 、DFS&BFS
        • 第七周:图(中)Floyd算法求最短路
        • 第八周:图(下)
        • 第九周:排序(上)归并&堆排序
        • 第十周:排序(下)
        • 第十一周:散列查找 & KMP
    • CS基础
      • 编译原理 实验一 词法分析器设计
      • 编译原理 实验二 LL(1)分析法程序
    • LeetCode
      • 冲刺春招-精选笔面试 66 题大通关
        • day1:21. 合并两个有序链表、146. LRU 缓存、25. K 个一组翻转链表
        • day2:14. 最长公共前缀、3. 无重复字符的最长子串、124. 二叉树中的最大路径和
        • day3:206. 反转链表、199. 二叉树的右视图、bytedance-016最短移动距离
        • day4:1. 两数之和、15. 三数之和、42. 接雨水
        • day5:7. 整数反转、215. 数组中的第K个最大元素、23. 合并K个升序链表
        • day6:33. 搜索旋转排序数组、54. 螺旋矩阵、bytedance-006. 夏季特惠
        • day7:53. 最大子数组和、152. 乘积最大子数组、41. 缺失的第一个正数
        • day8:20. 有效的括号、200. 岛屿数量、76. 最小覆盖子串
        • day9:105. 从前序与中序遍历序列构造二叉树、103. 二叉树的锯齿形层序遍历、bytedance-010. 数组组成最大数
        • day10:94. 二叉树的中序遍历、102. 二叉树的层序遍历、394. 字符串解码
        • day11:415. 字符串相加、5. 最长回文子串、72. 编辑距离
        • day12:64. 最小路径和、300. 最长递增子序列、bytedance-004. 机器人跳跃问题
        • day13:88. 合并两个有序数组、31. 下一个排列、4. 寻找两个正序数组的中位数
        • day14:121. 买卖股票的最佳时机、56. 合并区间、135. 分发糖果
        • day15:232. 用栈实现队列、22. 括号生成、128. 最长连续序列
        • day16:bytedance-007. 化学公式解析、129. 求根节点到叶节点数字之和、239. 滑动窗口最大值
        • day17:141. 环形链表、236. 二叉树的最近公共祖先、92. 反转链表 II
        • day18:322. 零钱兑换、198. 打家劫舍、 bytedance-003. 古生物血缘远近判定
        • day19:160. 相交链表、143. 重排链表、142. 环形链表 II
        • day20:704. 二分查找、43. 字符串相乘、bytedance-002. 发下午茶
        • day21题目:69. x 的平方根、912. 排序数组、887. 鸡蛋掉落
        • day22:151. 颠倒字符串中的单词、46. 全排列、2. 两数相加
      • 剑指 Offer
        • 剑指offer day1 栈与队列(简单)
        • 剑指offer day2 链表(简单)
        • 剑指offer day3 字符串(简单)
        • 剑指offer day4 查找算法(简单)
        • 剑指offer day5 查找算法(中等)
        • 剑指offer day6 搜索与回溯算法(简单)
        • 剑指offer day7 搜索与回溯算法(简单)
        • 剑指offer day8 动态规划(简单)
        • 剑指offer day9 动态规划(中等)
        • 剑指offer day10 动态规划(中等)
        • 剑指offer day11 双指针(简单)
        • 剑指offer day12 双指针(简单)
        • 剑指offer day13 双指针(简单)
        • 剑指offer day14 搜索与回溯算法(中等)
        • 剑指offer day15 搜索与回溯算法(中等)
        • 剑指offer day16 排序(简单)
        • 剑指offer day17 排序(中等)
      • 剑指 Offer 专项突击版
  • 前端进阶
    • React
      • 响应式系统与 React
      • React学习小记
      • Redux学习之Redux三原则、createSore原理及实现
    • Vue
    • TypeScript
      • TypeScript入门
      • TypeScript 类型体操练习
        • Easy题(13/13)
        • Middle(20/72)
    • 前端工程化
      • Webpack知识体系
    • Node
    • 前端动画与绘图
      • WebGL基础
      • 前端动画简介
      • Floating UI 使用经验分享 - Popover
      • Floating UI 使用经验分享 - Dialog
      • Three.js 学习
        • 学习记录
        • 资源记录
    • 前端性能优化
    • 跨端
      • RN 学习小记之使用 Expo 创建项目
    • 开源
    • SEO 优化
      • 搜索引擎优化 (SEO) 新手指南笔记
  • 笔面试记录
    • 面经集锦
      • 2022春暑期实习MetaApp一二面面经
      • 2022春暑期实习字节前端一面凉经
    • 笔试复盘
      • 2022春暑期实习-美团前端-笔试
      • 2022春暑期实习-360前端-笔试(AK)
      • 2022春暑期实习-京东前端-笔试
      • 2022春暑期实习-网易雷火前端-笔试(AK)
      • 2022春暑期实习-网易互联网前端-暑期实习笔试
由 GitBook 提供支持
在本页
  • 题单(中等)
  • 解答
  • 2・获取函数返回类型
  • 3・实现 Omit
  • 8・Readonly 2
  • 9・深度 Readonly ⭐
  • 10・元组转合集
  • 12・可串联构造器 ⭐⭐
  • 15・最后一个元素
  • 16・出堆
  • 20・Promise.all ⭐⭐⭐
  • 62・Type Lookup ⭐⭐
  • 106・Trim Left
  • 108・Trim ⭐
  • 110・Capitalize
  • 116・Replace ⭐
  • 119・ReplaceAll ⭐⭐
  • 191・追加参数
  • 296・Permutation ⭐⭐⭐⭐
  • 298・Length of String ⭐
  • 459・Flatten ⭐⭐⭐
  • 527・Append to object ⭐

这有帮助吗?

在GitHub上编辑
导出为 PDF
  1. 前端进阶
  2. TypeScript
  3. TypeScript 类型体操练习

Middle(20/72)

Middle题共72道,是类型体操的大头

上一页Easy题(13/13)下一页前端工程化

最后更新于2年前

这有帮助吗?

题单(中等)

解答

2・获取函数返回类型

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

例如:

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

type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"

正确解答:

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never

注意参数数组~

3・实现 Omit

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

Omit 会创建一个省略 K 中字段的 T 对象。

例如:

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

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

const todo: TodoPreview = {
  completed: false,
}

正确解答:

type MyOmit<T, K extends keyof T> = {
  [Key in keyof T as Key extends K ? never : Key]: T[Key]
}

乍一看很绕,其实搞懂 keyof 和 in 就明白了。

8・Readonly 2

实现一个通用 MyReadonly2<T, K>,它带有两种类型的参数 T和 K。

K指定应设置为 Readonly 的 T的属性集。如果未提供 K,则应使所有属性都变为只读,就像普通的 Readonly<T>一样。

例如

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

const todo: MyReadonly2<Todo, 'title' | 'description'> = {
  title: 'Hey',
  description: 'foobar',
  completed: false,
}

todo.title = 'Hello' // Error: cannot reassign a readonly property
todo.description = 'barFoo' // Error: cannot reassign a readonly property
todo.completed = true // OK

正确解答

type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [Key in K]: T[Key]
} & Omit<T, K>

9・深度 Readonly ⭐

实现一个通用的 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。 您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

例如

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

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

type Todo = DeepReadonly<X> // should be same as `Expected`

正确解答

type DeepReadonly<T extends Object> = {
  readonly [Key in keyof T]: T[Key] extends Record<any, any>
    ? T[Key] extends Function
      ? T[Key]
      : DeepReadonly<T[Key]>
    : T[Key]
}

递归解决,注意特判 Funtion 的情况

10・元组转合集

实现泛型 TupleToUnion<T>,它返回元组所有值的合集。

例如

type Arr = ['1', '2', '3']

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

解答: 没想太多的递归实现 👇

type TupleToUnion<T> = T extends [infer V, ...infer Rest] ? V | TupleToUnion<Rest> : never

翻了翻 issue 发现的另一个实现,确实哦!

type TupleToUnion<T extends any[]> = T[number]

12・可串联构造器 ⭐⭐

  • 我的解答

在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?

在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。

例如

type Chainable = {
  option(key: string, value: any): any
  get(): any
}

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 只会被使用一次。

正确解答:注意 Key 必须为之前没取过的,或是 Value 类型与之前不同的(用例 2 和 3)。

type Chainable<Obj extends Record<string, any> = {}> = {
  option<K extends string, V>(
    key: K extends keyof Obj ? (V extends Obj[K] ? never : V) : K,
    value: V,
  ): Chainable<Omit<Obj, K> & Record<K, V>>
  get(): Obj
}

15・最后一个元素

实现一个通用 Last<T>,它接受一个数组 T并返回其最后一个元素的类型。

正确解答:基本送分题

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

16・出堆

实现一个通用 Pop<T>,它接受一个数组 T,并返回一个由数组 T的前 length-1 项以相同的顺序组成的数组。

例如

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

type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]

额外:同样,您也可以实现 Shift,Push和 Unshift吗?

正确解答:看了小册后不难做出

type Pop<T extends any[]> = T extends [...infer Rest, infer L] ? Rest : T
type Push<T extends any[], Arr extends any[]> = [...T, ...Arr]
type Shift<T extends any[]> = T extends [infer F, ...infer Rest] ? Rest : T
type Unshift<T extends any[], Arr extends any[]> = [...Arr, ...T]

20・Promise.all ⭐⭐⭐

键入函数 PromiseAll,它接受 PromiseLike 对象数组,返回值应为 Promise<T>,其中 T是解析的结果数组。

const promise1 = Promise.resolve(3)
const promise2 = 42
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo')
})

// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)

正确解答:

declare function PromiseAll<Args extends readonly unknown[]>(
  values: readonly [...Args],
): Promise<{ [Key in keyof Args]: Args[Key] extends Promise<infer V> ? V : Args[Key] }>

62・Type Lookup ⭐⭐

有时,您可能希望根据某个属性在联合类型中查找类型。

在此挑战中,我们想通过在联合类型 Cat | Dog中搜索公共 type字段来获取相应的类型。换句话说,在以下示例中,我们期望 LookUp<Dog | Cat, 'dog'>获得 Dog,LookUp<Dog | Cat, 'cat'>获得 Cat。

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`

正确解答:第一反应如下

type LookUp<U, T extends string> = U extends { type: infer S } & Record<any, any> ? (S extends T ? U : never) : never
type LookUp<U, T extends string> = U extends { type: T } ? U : never

106・Trim Left

实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。

例如

type trimed = TrimLeft<'  Hello World  '> // 应推导出 'Hello World  '
type Space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${Space}${infer NewS}` ? TrimLeft<NewS> : S

108・Trim ⭐

实现Trim<T>,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。

例如

type trimed = Trim<'  Hello World  '> // expected to be 'Hello World'

正确解答:结合上一题,易得 TrimRight 的实现方法,二者结合即可实现 Trim

type Space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${Space}${infer NewS}` ? TrimLeft<NewS> : S
type TrimRight<S extends string> = S extends `${infer NewS}${Space}` ? TrimLeft<NewS> : S
type Trim<S extends string> = TrimLeft<TrimRight<S>>
type Space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${Space}${infer NewS}` | `${infer NewS}${Space}` ? Trim<NewS> : S

110・Capitalize

实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。

例如

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

正确解答:还是通过模式匹配,使用内置类型 Uppercase 进行大写转换

type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S

116・Replace ⭐

实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

例如

type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'

正确解答:注意 From 为空的情况

type Replace<S extends string, From extends string, To extends string> = S extends `${infer Pre}${From}${infer Post}`
  ? From extends ''
    ? S
    : `${Pre}${To}${Post}`
  : S

119・ReplaceAll ⭐⭐

实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

例如

type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'

正确解答:在上题基础上,递归!

type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Pre}${From}${infer Post}`
  ? From extends ''
    ? S
    : `${Pre}${To}${ReplaceAll<Post, From, To>}`
  : S

191・追加参数

实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

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

type Result = AppendArgument<Fn, boolean>
// 期望是 (a: number, b: string, x: boolean) => number

正确解答:显而易见,本题 easy

type AppendArgument<Fn extends (...args: any) => any, A> = Fn extends (...args: infer Args) => infer R
  ? (...args: [...Args, A]) => R
  : never

296・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']

正确解答:题目字越少,难度越大(x)

type Permutation<T, Rest = T> = [T] extends [never] ? [] : Rest extends Rest ? [T, ...Permutation<Exclude<T, Rest>>] : []

298・Length of String ⭐

计算字符串的长度,类似于 String#length 。

正确解答:不讲武德版当然就是 type LengthOfString<S extends string> = S['length'] 当然,讲武德版就是这样 ⬇️

type LengthOfString<S extends string, T extends string[] = []> = S extends `${string}${infer Rest}`
  ? LengthOfString<Rest, [string, ...T]>
  : T['length']

459・Flatten ⭐⭐⭐

在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。

例如:

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

正确解答:经典的数组拍平,需要注意的是...展开需在外层,Item extends any[] ? ...Flatten<Item> : Item 是不行的......

type Flatten<Arr extends any[]> = Arr extends [infer Item, ...infer Rest]
  ? [...(Item extends any[] ? Flatten<Item> : [Item]), ...Flatten<Rest>]
  : []

527・Append to object ⭐

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

例如:

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

正确解答:先将交叉类型整出来,再把交叉类型遍历一遍。

type AppendToObject<T extends Record<string, any>, U extends string, V> = {
  [Key in keyof T]: T[Key]
} & {
  [key in U]: V
} extends infer Obj
  ? { [K in keyof Obj]: Obj[K] }
  : never
type AppendToObject<T, U extends keyof any, V> = {
  [K in keyof T | U]: K extends keyof T ? T[K] : V
}

结合上面的 和简单题中的 可得出该题的答案。

疑惑吗?疑惑就对了,请看看这个 pr :

翻了翻 issue,很巧妙地如下:

正确解答:非常经典的!

也可以用:

本题实现全排列,同时要利用联合类型的分散传参这个特性

翻 issue 的时候翻到了这个优秀的解答:

[T] extends [never] 是由于 never 无法被准确判断,任何extends never的条件语句都会返回never,而我们需要返回的是空数组,在小册中的 有提到这一点。

看到一种很有趣的

2・获取函数返回类型
3・实现 Omit
8・Readonly 2
9・深度 Readonly
10・元组转合集
12・可串联构造器
15・最后一个元素
16・出堆
20・Promise.all
62・Type Lookup
106・Trim Left
108・Trim
110・Capitalize
116・Replace
119・ReplaceAll
191・追加参数
296・Permutation
298・Length of String
459・Flatten
527・Append to object
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
实现 Omit
实现 Readonly
接受挑战
我的解答
接受挑战
我的解答
接受挑战
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
Variadic tuple types
接受挑战
我的解答
一个解答
接受挑战
我的解答
模式提取做匹配
接受挑战
我的解答
第二种解法
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
Distributive Conditional Types
TS 类型体操笔记 - 296 Permutation
套路六:特殊特性要记清
接受挑战
我的解答
接受挑战
我的解答
接受挑战
我的解答
解法