TypeScript中类型安全的柯里化pick()与pluck()实现难题
类型安全的柯里化 pick() 和 pluck() 实现
原代码存在的问题
pick()返回Partial<T>,这会将所有属性标记为可选,但实际上我们期望返回的对象包含所有指定属性(类型与原对象一致),因为传入的obj必然包含这些属性。pluck()的返回类型无法正确推断,TypeScript 无法通过reduce追踪嵌套属性访问的类型,导致返回值类型不准确。
修正后的实现
类型安全的 pick()
export const pick = <K extends readonly string[]>(...props: K) => <T extends Record<K[number], unknown>>(obj: T): Pick<T, K[number]> => { return props.reduce((result, nextKey) => { result[nextKey] = obj[nextKey]; return result; }, {} as Pick<T, K[number]>); };
说明:
- 使用
Pick<T, K[number]>作为返回类型,确保返回对象仅包含指定属性,且类型与原对象对应属性完全一致。 - 通过
{} as Pick<T, K[number]>初始化累加器,让 TypeScript 识别其正确类型。 - 泛型约束
T extends Record<K[number], unknown>确保传入对象包含所有指定属性。
类型安全的 pluck()
首先定义递归辅助类型处理嵌套属性的类型推导:
type Pluck<T, K extends readonly string[]> = K extends [infer FirstKey, ...infer RestKeys] ? FirstKey extends keyof T ? RestKeys extends readonly string[] ? Pluck<T[FirstKey], RestKeys> : never : never : T;
然后实现 pluck() 函数:
export const pluck = <K extends readonly string[]>(...props: K) => <T>(obj: T): Pluck<T, K> => { return props.reduce((current, key) => current[key], obj) as Pluck<T, K>; };
说明:
- 辅助类型
Pluck<T, K>递归遍历属性列表K,逐步推导嵌套属性的最终类型。 - 当属性列表为空时,直接返回原对象类型
T。 - 使用
as Pluck<T, K>帮助 TypeScript 识别reduce结果的正确类型(TypeScript 无法自动推导递归嵌套类型)。
使用示例
// 使用 pick() const pickNameAndAge = pick('name', 'age'); const user = { name: 'Alice', age: 30, email: 'alice@example.com' }; const picked = pickNameAndAge(user); // picked 类型为 { name: string; age: number } // 使用 pluck() const pluckAddressCity = pluck('address', 'city'); const userWithAddress = { name: 'Bob', address: { city: 'New York', zip: '10001' } }; const city = pluckAddressCity(userWithAddress); // city 类型为 string
内容的提问来源于stack exchange,提问作者Adam B.
相关产品推荐
相关产品推荐

