TypeScript如何为带索引签名的复杂对象函数做准确类型标注
问题描述
概述
我编写了一个用于返回对象指定位置值的函数,逻辑本身无特殊之处。当前遇到的问题是:该对象是已知索引与索引签名的混合结构,函数出现类型匹配异常。我暂不确定是自身函数类型编写错误,还是TS校验bug,希望获得帮助解决该类型问题,若能提供简化方案也十分欢迎。
函数代码
function getCounts<Entity extends EntityType>(entity: Entity, id: string): Counts[Entity][string] { return counts[entity][id] ?? []; // 此处抛出错误 }
报错信息
Type 'CountsWithEntity<Entity1> | CountsWithEntity<Entity2>' is not assignable to type 'Counts[Entity][string]'. Type 'CountsWithEntity<Entity1>' is not assignable to type 'Counts[Entity][string]'.
实际运行表现
该函数实际运行正常,调用时的类型推断结果也符合预期。
完整复现代码
type Entity1 = { hello: string }; type Entity2 = { world: string }; enum EntityType { entity1 = 'hello', entity2 = 'world', } type Entities = Entity1 | Entity2; type EntityMap = { [EntityType.entity1]: Entity1; [EntityType.entity2]: Entity2; }; type CountsWithEntity<Entity extends Entities> = Array<{ count: number; entity: Entity; }>; type Counts = { [entityKey in EntityType]: { [id: string]: CountsWithEntity<EntityMap[entityKey]>; }; }; const counts: Counts = { [EntityType.entity1]: { id1: [ { count: 2, entity: { hello: 'hi' }, }, { count: 1, entity: { hello: 'you' }, }, ], id2: [ { count: 3, entity: { hello: 'bye' }, }, { count: 4, entity: { hello: 'cya' }, }, ], }, [EntityType.entity2]: { id3: [ { count: 6, entity: { world: 'earth' }, }, { count: 5, entity: { world: 'place' }, }, ], id4: [ { count: 8, entity: { world: 'dirt' }, }, { count: 7, entity: { world: 'huh?' }, }, ], }, }; function getCounts<Entity extends EntityType>(entity: Entity, id: string): Counts[Entity][string] { return counts[entity][id] ?? []; // 抛出错误 } console.log(counts[EntityType.entity1].id1); // 预期返回`[{ count: 2, entity: { hello: 'hi' } }, { count: 1, entity: { hello: 'you' } }]`,实际运行正常无TS报错 console.log(getCounts(EntityType.entity1, 'id1')); // 预期返回同上,运行正常无TS报错 console.log(getCounts(EntityType.entity2, 'id3')[0].entity.world); // 预期返回`"earth"`,运行正常无TS报错
解决方案
问题原因
这个报错是TypeScript现有类型系统对未具象化泛型的嵌套索引推导局限性导致的,不属于TS bug,你的函数逻辑和调用侧的类型推断本身都是正确的。
当泛型Entity还没有在函数内部被具象为具体的枚举值时,TS不会深层关联Counts[Entity]和Entity的对应关系,只会把counts[entity]推导为Counts[EntityType.entity1] | Counts[EntityType.entity2]的联合类型,取string索引后的结果自然也没法直接匹配你声明的Counts[Entity][string]返回类型。
实现方案
方案1:类型断言(最简实现)
因为逻辑本身没有问题,直接对返回值做类型断言即可,不会影响调用侧的类型推断效果:
function getCounts<Entity extends EntityType>(entity: Entity, id: string): Counts[Entity][string] { return counts[entity][id] ?? [] as Counts[Entity][string]; }
方案2:函数重载(无断言实现)
如果不想使用类型断言,可以用函数重载拆分外部类型签名和内部实现逻辑,同样可以解决报错:
// 对外暴露的精确类型签名 function getCounts<Entity extends EntityType>(entity: Entity, id: string): Counts[Entity][string] // 内部实现用宽松类型 function getCounts(entity: EntityType, id: string) { return counts[entity][id] ?? [] }
两种方案都可以完全保留原有调用侧的类型推断能力,不会影响实际运行效果。
内容的提问来源于stack exchange,提问作者Aerophite
相关产品推荐
相关产品推荐

