TypeScript如何提取联合类型指定名称元素及专属属性
TypeScript 泛型类型推导异常修复方案
问题描述
使用TypeScript开发时,遇到类型定义无法实现预期推导效果的问题:
- 最初定义了
ElementData类型,对该类型定义的合理性暂不确定:
type ElementData<TData extends any> = TData;
- 基于上述类型定义
Element接口:
interface Element<TName extends string, TElementData extends ElementData<any>> { name: TName; data: TElementData; }
- 定义存储
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
相关产品推荐
相关产品推荐

