如何在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
相关产品推荐
相关产品推荐

