手写 MyPick 和 MyPartial
Implement Pick and Partial by hand
一句话:映射类型(mapped type)是 「对属性名的联合做一次循环」。{ [K in keyof T]?: T[K] } 这一行就是 Partial 的完整实现:逐个取出 T 的属性名,抄下原类型, 加上可选修饰符。
逐段拆这一行:
keyof T—— 属性名的联合。User的就是"id" | "name" | "email"。[K in keyof T]—— 映射本体: 对联合里的每个成员各生成一个属性,K是循环变量。?:—— 给生成的属性加可选修饰符。 同一个位置还能写readonly;写成-?是反向操作 —— 去掉可选,这正是Required的实现。T[K]—— 索引访问类型(indexed access type): 原属性是什么类型,就抄什么类型。
MyPick 多一个泛型约束(generic constraint):K extends keyof T。没有它,调用方可以给 K 传任何东西,T[P] 也就没法算。有了它,MyPick<User, "age"> 在调用处直接报Type '"age"' does not satisfy the constraint, 而不是悄悄得出一个错的类型。
会追问:「Omit 怎么用 Pick 组合出来?」——Pick<T, Exclude<keyof T, K>>: 先用 Exclude 从全部键里去掉 K,剩下的交给 Pick。 能再补一句「官方 Omit 的约束是 keyof any, 比 keyof T 宽,所以官方版允许去掉一个不存在的键」, 说明你真读过 lib.es5.d.ts —— 背 API 的人和读过实现的人,在这一问上分开。
In one line: a mapped type is one loop over a union of property names. The single line { [K in keyof T]?: T[K] } is the whole implementation of Partial: take each property name of T, copy its original type, add the optional modifier.
Reading it piece by piece:
keyof T— the union of property names. ForUserthat is"id" | "name" | "email".[K in keyof T]— the mapping itself: generate one property per member of the union, withKas the loop variable.?:— add the optional modifier to the generated property. The same slot also takesreadonly; and-?goes the other way — it removes optionality, which is exactly howRequiredis implemented.T[K]— an indexed access type: whatever type the original property had, copy it.
MyPick needs one extra generic constraint: K extends keyof T. Without it, callers may pass anything as K, and T[P] cannot be computed. With it, MyPick<User, "age"> fails right at the call site with Type '"age"' does not satisfy the constraint, instead of quietly producing a wrong type.
Follow-up: “How do you build Omit out of Pick?” — Pick<T, Exclude<keyof T, K>>: Exclude removes K from the full key set, Pick keeps the rest. Add that the official Omit constrains K to keyof any — looser than keyof T, so the official one lets you omit a key that does not exist — and it shows you have actually read lib.es5.d.ts. People who memorized the API and people who read the implementation part ways on this one question.