基于TypeScript条件类型实现深度选取方法的技术问询
嘿,我最近也在捣鼓TypeScript的条件类型做深度字段选取,刚好能帮到你!先理清楚咱们的需求:你想要一个DeepPick类型,能根据指定的深度路径,从数据模型里提取对应结构,然后让loadUser返回这个精确的类型对吧?
先拿你提到的User类型举个具体例子:
// 定义一个嵌套的User模型 type User = { id: string; name: { first: string; last: string; }; posts: { id: number; title: string; content: string; }[]; }; // 期望调用loadUser后,返回只包含id、name.first、posts.id的结构 const user = loadUser<User, 'id' | 'name.first' | 'posts.id'>(); // 希望user的类型自动推导为: // { // id: string; // name: { first: string }; // posts: { id: number }[]; // }
你之前遇到的问题,大概率是直接用T[K]时,没办法处理'name.first'这种嵌套路径——因为T的键是'name',根本不存在'name.first'这个键,所以TypeScript会报错。另外数组类型的嵌套选取(比如posts.id)也容易被忽略,需要单独处理。
结合TypeScript的条件类型、模板字符串类型和递归,咱们可以实现一个完整的DeepPick:
// 辅助类型:判断是否是对象(排除函数) type IsObject<T> = T extends object ? T extends Function ? false : true : false; // 核心深度选取类型 type DeepPick<T, K> = // 处理数组情况:如果T是数组,就对数组元素执行DeepPick,返回数组类型 T extends (infer U)[] ? DeepPick<U, K>[] : // 如果是对象类型,继续处理路径 IsObject<T> extends true ? // 情况1:K是T的直接键,直接提取该字段 K extends keyof T ? { [P in K]: T[P] } : // 情况2:K是嵌套路径,用模板字符串拆分出第一级键和剩余路径 K extends `${infer Key}.${infer Rest}` ? // 确保第一级键存在于T中,然后递归处理剩余路径 Key extends keyof T ? { [P in Key]: DeepPick<T[Key], Rest> } : never // 路径不存在时返回never,触发类型检查 : never : never; // 非对象类型直接返回never(避免基础类型的无效选取)
然后实现loadUser函数就很简单了:
function loadUser<T, K extends string>(): DeepPick<T, K> { // 这里替换成实际的异步请求逻辑,比如fetch后端接口 // 类型断言只是为了绕过编译检查,实际返回要匹配DeepPick的结构 return {} as DeepPick<T, K>; }
现在测试一下,你会发现类型推导完全符合预期:
const user = loadUser<User, 'id' | 'name.first' | 'posts.id'>(); user.id; // 类型是string ✅ user.name.first; // 类型是string ✅ user.posts[0].id; // 类型是number ✅ // 如果尝试访问user.name.last,TypeScript会直接报错,因为我们没选取这个字段 ❌
几个需要注意的点:
- 这个实现依赖TypeScript 4.1+的模板字符串类型,你用的
typescript@next肯定支持; - 如果指定的路径不存在(比如
'name.middle'),DeepPick会返回never类型,TypeScript会在编译阶段提醒你; - 目前只处理了对象和数组,如果你需要处理其他嵌套类型(比如Map、Set),可以扩展
IsObject和递归逻辑。
内容的提问来源于stack exchange,提问作者casieber
相关产品推荐
相关产品推荐

