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

TypeScript如何提取联合类型指定名称元素及专属属性

TypeScript 泛型类型推导异常修复方案

问题描述

使用TypeScript开发时,遇到类型定义无法实现预期推导效果的问题:

  1. 最初定义了ElementData类型,对该类型定义的合理性暂不确定:
type ElementData<TData extends any> = TData;
  1. 基于上述类型定义Element接口:
interface Element<TName extends string, TElementData extends ElementData<any>> {
  name: TName;
  data: TElementData;
}
  1. 定义存储Element联合类型的Container接口:
interface Container<TElement extends Element<string, ElementData<any>>> {
  elements: TElement[];
  getElement: <TElementName extends TElement["name"]>(
    elementName: TElementName,
  ) => Element<TElementName, TElement["data"]> extends infer UElement extends Element<TElementName, any>
    ? UElement
    : never;
}

尝试使用infer关键字做类型提取,但未找到正确的使用位置,实际运行时类型推导不符合预期。

复现代码

type ElementData<TData extends any> = TData;

interface Element<TName extends string, TElementData extends ElementData<any>> {
  name: TName;
  data: TElementData;
}

interface Container<TElement extends Element<string, ElementData<any>>> {
  elements: TElement[];
  getElement: <TElementName extends TElement["name"]>(
    elementName: TElementName,
  ) => Element<TElementName, TElement["data"]> extends infer UElement extends Element<TElementName, any>
    ? UElement
    : never;
}

const container = {} as Container<Element<"one", { oneKey: 1 }> | Element<"two", { twoKey: 2 }>>;

const element = container.getElement("one");
console.log(element.name === "one");
console.log(element.name === "two"); // 符合预期报错
console.log(element.data.oneKey === 1); // 意外报错,期望无报错
console.log(element.data.twoKey === 2); // 符合预期报错

const element2 = container.getElement("two");
console.log(element2.name === "one"); // 符合预期报错
console.log(element2.name === "two");
console.log(element2.data.oneKey === 1); // 符合预期报错
console.log(element2.data.twoKey === 2); // 意外报错,期望无报错

预期效果

调用getElement方法传入对应元素名称时,返回值的name字段类型正确收窄为传入的名称字面量类型,同时data字段精准匹配该元素对应的专属属性类型,无多余类型报错。

修复方案

原写法的核心问题是返回值类型直接取TElement["data"],得到的是所有元素data类型的联合,没有和传入的TElementName做关联匹配,不需要嵌套多余的infer逻辑,直接使用TypeScript内置的Extract工具类型,从TElement联合类型中提取name属性匹配传入名称的成员即可。
修正后的Container定义:

interface Container<TElement extends Element<string, ElementData<any>>> {
  elements: TElement[];
  getElement: <TElementName extends TElement["name"]>(
    elementName: TElementName,
  ) => Extract<TElement, { name: TElementName }>;
}

另外初始定义的ElementData<TData extends any> = TData属于无意义的泛型包装,可以直接移除简化类型定义,完整可用代码如下:

interface Element<TName extends string, TData> {
  name: TName;
  data: TData;
}

interface Container<TElement extends Element<string, any>> {
  elements: TElement[];
  getElement: <TElementName extends TElement["name"]>(
    elementName: TElementName,
  ) => Extract<TElement, { name: TElementName }>;
}

const container = {} as Container<Element<"one", { oneKey: 1 }> | Element<"two", { twoKey: 2 }>>;

const element = container.getElement("one");
console.log(element.name === "one");
// console.log(element.name === "two"); // 类型报错,符合预期
console.log(element.data.oneKey === 1); // 无类型报错,符合预期
// console.log(element.data.twoKey === 2); // 类型报错,符合预期

const element2 = container.getElement("two");
// console.log(element2.name === "one"); // 类型报错,符合预期
console.log(element2.name === "two");
// console.log(element2.data.oneKey === 1); // 类型报错,符合预期
console.log(element2.data.twoKey === 2); // 无类型报错,符合预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 16:36:27