TypeScript如何通过动态键指定索引访问类型?
基于动态键收窄TypeScript类型的解决方案
问题根源
你的代码无法得到预期类型的核心原因:
- TypeScript对
Array.map的类型处理默认针对数组而非元组,遍历["body", "header"] as const时,每个迭代的field会被推断为联合类型"body"|"header",而非对应位置的字面量类型。 - 联合类型的索引访问无法让TypeScript追踪当前迭代的具体字段,最终类型退化为
any或宽泛联合类型,而非精准的目标类型。
简单场景解决方案
从单个对象按元组键提取值时,可通过元组映射类型+辅助函数保留类型关联:
// 定义元组映射类型,将键元组转换为对应值的元组类型 type PickTuple<T, K extends readonly (keyof T)[]> = { [I in keyof K]: T[K[I]]; }; // 辅助函数,利用类型推断保留元组结构 function pickTuple<T, K extends readonly (keyof T)[]>(obj: T, keys: K): PickTuple<T, K> { return keys.map(key => obj[key]) as PickTuple<T, K>; } // 使用示例 const response = { body: { data: "foo" }, header: { status: 200 } }; const [a, b] = pickTuple(response, ["body", "header"] as const); // a 类型为 { data: string }, b 类型为 { status: number }
若不想写辅助函数,可直接用类型断言(需重复键名,冗余性较高):
const [a, b] = (["body", "header"] as const).map(field => response[field]) as [typeof response["body"], typeof response["header"]];
复杂场景(Responses对象)解决方案
针对多响应对象提取指定字段的场景,通过映射类型精准描述结果结构,配合辅助函数实现类型推断:
// 基础类型定义 type HttpResponse = { body: { http1Body: string }, header: { http1Header: number } }; type Http2Response = { body: { http2Body: string }, header: { http2Header: number } }; type Responses = { http1: HttpResponse, http2: Http2Response }; // 从Responses中提取指定字段的类型 type ExtractFieldFromResponses<F extends keyof HttpResponse | keyof Http2Response> = { [K in keyof Responses]: Responses[K][F]; }; // 将字段元组转换为对应结果的元组类型 type ExtractFieldsTuple<F extends readonly ("body"|"header")[]> = { [I in keyof F]: ExtractFieldFromResponses<F[I]>; }; // 辅助函数实现逻辑+类型约束 function extractFieldsFromResponses< R extends Record<string, { body: any, header: any }>, F extends readonly ("body"|"header")[] >(responses: R, fields: F): ExtractFieldsTuple<F> { return fields.map(field => { const result = {} as ExtractFieldFromResponses<F[number]>; for (const key in responses) { result[key as keyof R] = responses[key][field]; } return result; }) as ExtractFieldsTuple<F>; } // 使用示例 const responses = { http1: { body: { http1Body: "test" }, header: { http1Header: 200 } }, http2: { body: { http2Body: "test2" }, header: { http2Header: 404 } } } as const; const [bodies, headers] = extractFieldsFromResponses(responses, ["body", "header"] as const); // bodies 类型:{ http1: { http1Body: string }, http2: { http2Body: string } } // headers 类型:{ http1: { http1Header: number }, http2: { http2Header: number } }
若不想用辅助函数,可直接通过类型断言指定结果元组:
const [bodies, headers] = (["body", "header"] as const).map(field => Object.fromEntries( Object.entries(responses).map(([key, value]) => [key, value[field]]) ) ) as [ { [K in keyof Responses]: Responses[K]["body"] }, { [K in keyof Responses]: Responses[K]["header"] } ];
核心思路总结
TypeScript默认不会自动关联元组迭代中键与值的类型,需通过映射类型明确描述每个位置的预期类型,再配合辅助函数或类型断言让编译器识别这种关联,实现动态键下的类型收窄,避免冗余的手动类型声明。
内容的提问来源于stack exchange,提问作者bpstrngr
相关产品推荐
相关产品推荐

