You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:50:46