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

TypeScript泛型实例集合的类型安全动态访问问题

解决方案

问题核心

你遇到的两个类型错误根源在于:

  1. 直接用string索引TEMPLATE_MAP时,TypeScript无法推断具体模板类型,导致template被推断为any。
  2. 显式声明Record<string, ReturnType<typeof createTemplate>>时,由于函数参数的逆变特性,具体模板的use函数(需要特定形状的参数)无法赋值给宽泛类型的use(接受任意Record<string, string | number>)。

正确实现方案

通过保留模板的原始类型关联,同时兼容动态索引需求,实现类型安全:

1. 保留模板的具体类型信息

使用as const让TypeScript自动推断TEMPLATE_MAP的完整类型,保留每个模板的pick和use的类型关联:

const TEMPLATE_MAP = {
  One: FirstTemplate,
  Two: SecondTemplate,
} as const;

2. 定义辅助类型(可选,提升代码可读性)

提取TEMPLATE_MAP的键和值类型,避免重复书写:

type TemplateMap = typeof TEMPLATE_MAP;
type TemplateKey = keyof TemplateMap;

3. 类型安全的动态取值函数

先做运行时检查确保key合法,再通过类型断言让TypeScript识别具体模板类型,保证pick和use的类型匹配:

const evaluateTemplate = (items: Items, key: string) => {
  if (!(key in TEMPLATE_MAP)) {
    throw new Error("Template not found");
  }
  // 断言key为合法键类型,让TypeScript识别具体模板的类型关联
  const template = TEMPLATE_MAP[key as TemplateKey];
  const picked = template.pick(items);
  // 此时picked的类型完全匹配template.use的参数类型,无需额外断言
  return template.use(picked);
};

(可选)严格限制键类型

如果不需要支持任意string,可以直接将key参数限制为TEMPLATE_MAP的合法键,进一步提升类型安全性:

const evaluateTemplate = (items: Items, key: TemplateKey) => {
  const template = TEMPLATE_MAP[key];
  const picked = template.pick(items);
  return template.use(picked);
};

为什么之前的尝试失败?

当你用Record<string, ReturnType<typeof createTemplate>>声明TEMPLATE_MAP时,ReturnType<typeof createTemplate>会被推断为宽泛的:

{ pick: (items: Items) => Record<string, string | number>; use: (props: Record<string, string | number>) => string; }

但实际每个模板的use函数需要的是更具体的参数类型(比如{ foo: string; bar: number })。由于函数参数是逆变的:更宽泛的参数类型不能被更具体的参数类型替代,因此出现类型不兼容错误。

而as const保留了TEMPLATE_MAP的原始类型后,每个模板的pick返回值和use参数的类型关联被完整保留,TypeScript能自动匹配两者的类型,实现类型安全。

内容的提问来源于stack exchange,提问作者Oscar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:40:55