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

如何在TypeScript中动态推断类型?解决注册元素类型报错

动态类型推断实现元素注册的类型扩展

我想通过registerElement(...)方法从代码不同部分注册多个元素,但TypeScript无法推断这些新类型,会出现Type "pack" is not assignable to type "common"这类错误。

预期行为是注册新元素(比如示例中的"pack")后就能直接使用,由于元素数量不确定且会随代码增减变化,不想用硬编码的联合类型:

type ElementTypes = Common | Pack | ...N

我知道TypeScript类型推断发生在编译时而非运行时,以下示例无法正常工作:

interface BaseElement<T, C> {
    name: T,
    nested: C[]
}

interface Common extends BaseElement<'common', string> {}

const common: Common = {
    name: 'common',
    nested: []
}

const myElements = {common}

type ElementTypes = keyof typeof myElements

const foo: ElementTypes = 'common'; // Ok.

// Assign a new element
interface Pack extends BaseElement<'pack', Common> {}

const pack: Pack = {
    name: 'pack',
    nested: []
}

Object.assign(myElements, {pack});
const bar: ElementTypes = 'pack'; // Not working.

解决方案:泛型驱动的可扩展注册机制

要在编译时动态扩展类型,得利用TypeScript的泛型合并特性,用函数封装注册逻辑,让类型随每次注册自动更新。

1. 基础类型定义

先明确基础元素和容器的类型结构:

interface BaseElement<T extends string, C> {
    name: T,
    nested: C[]
}

// 元素容器类型,支持动态扩展
type ElementContainer<Elements extends Record<string, BaseElement<any, any>> = {}> = Elements;

2. 实现注册函数

写一个带泛型的registerElement函数,它会返回合并了新元素类型的容器:

function registerElement<
    Existing extends ElementContainer,
    NewElement extends BaseElement<string, any>
>(container: Existing, element: NewElement): Existing & Record<NewElement['name'], NewElement> {
    return { ...container, [element.name]: element };
}

这里用Existing & Record<NewElement['name'], NewElement>把已有类型和新元素类型合并,TypeScript会自动推断扩展后的完整类型。

3. 使用示例

逐步注册元素,类型会自动同步更新:

// 初始注册common元素
interface Common extends BaseElement<'common', string> {}
const common: Common = { name: 'common', nested: [] };
let myElements = registerElement({}, common);

// 当前ElementTypes为'common'
type ElementTypes = keyof typeof myElements;
const foo: ElementTypes = 'common'; // 正常

// 注册pack元素
interface Pack extends BaseElement<'pack', Common> {}
const pack: Pack = { name: 'pack', nested: [] };
myElements = registerElement(myElements, pack);

// ElementTypes自动扩展为'common' | 'pack'
const bar: ElementTypes = 'pack'; // 无类型错误

4. 可选:避免重复注册

如果要防止同名元素重复注册,可以给泛型加约束,让重复名称在编译时直接报错:

function registerElement<
    Existing extends ElementContainer,
    NewElement extends BaseElement<string, any>
>(
    container: Existing,
    element: NewElement extends { name: infer T } ? T extends keyof Existing ? never : NewElement : NewElement
): Existing & Record<NewElement['name'], NewElement> {
    return { ...container, [element.name]: element };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:35:39