Yuzhe's Blog

yuzhes

实现 Pick Pick

实现 Pick Pick

题目链接 / Challenge Link

题目 / Challenge

不使用内置工具类型 Pick<T, K>,自己实现一个 MyPick<T, K>

Implement the built-in Pick<T, K> without using it.

例如:

For example:

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

type TodoPreview = MyPick<Todo, 'title' | 'completed'>
// { title: string; completed: boolean }

解答 / Solution

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

核心思路很直接:K 就是我们要保留的属性集合,而映射类型可以让我们遍历这些属性并重新构造一个对象类型。

The core idea is simple: K is the subset of keys we want, and mapped types let us build a new object type by iterating over those keys.

逐步拆解 / Step by step

1. 约束 K 必须来自 T 的键

1. Restrict K to keys from T

K extends keyof T

这样可以阻止非法属性名,比如 MyPick<Todo, 'foo'>

This prevents invalid keys such as MyPick<Todo, 'foo'>.

2. 遍历 K

2. Iterate over K

[P in K]

它的意思是:对于 K 中的每一个属性名 P,都在新类型里生成一个同名属性。

This means: for every key P inside K, create one property in the new type.

3. 复用原对象上的属性类型

3. Reuse the original property type

T[P]

这里用的是索引访问类型,它会从 T 上取出属性 P 对应的值类型。

This is an indexed access type. It reads the value type of property P from T.

合起来就是:

Putting everything together:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

推导过程 / Walkthrough

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

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

这里 K'title' | 'completed',所以映射类型只会遍历这两个属性:

K is 'title' | 'completed', so the mapped type only iterates over these two keys:

type Result = {
  title: Todo['title']
  completed: Todo['completed']
}

展开后得到:

After substitution:

type Result = {
  title: string
  completed: boolean
}

为什么这样可行 / Why this works

Pick 本质上就是一次“按指定键重建对象”。它不像 Omit 那样需要通过 never 过滤属性,因为这里要保留哪些键已经明确写在 K 里了,直接遍历即可。

Pick is essentially a filtered object reconstruction. Unlike Omit, it does not need key remapping or never. We already know exactly which keys to keep, so we just iterate over them.

核心要点 / Key Takeaways