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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 22:30:05