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

TypeScript泛型适配Record<string|number>出现类型错误的原因

问题:TypeScript keyBy泛型函数报错:Type '{}' is not assignable to type 'Record<Key, T>'

我原本实现了一个keyBy函数,用来把对象数组转换成以指定数字键为索引的字典:

export function keyBy<T>(items: T[] | undefined, extractId: (item: T) => number): Record<number, T> {
  const obj: Record<number, T> = {};

  if (items) {
    items.forEach(item => {
      const id = extractId(item);
      obj[id] = item;
    });
  }

  return obj;
}

示例用法:

const users = [user1, user2, ...];
const usersById = keyBy(users, u => u.id);

现在我想让这个函数同时支持字符串和数字类型的键,于是修改成了下面的代码:

export function keyBy<Key extends string | number, T>(items: T[] | undefined, extractId: (item: T) => Key): Record<Key, T> {
  const obj: Record<Key, T> = {};

  if (items) {
    items.forEach(item => {
      const id = extractId(item);
      obj[id] = item;
    });
  }

  return obj;
}

结果出现了类型错误:Type '{}' is not assignable to type 'Record<Key, T>'. ts(2322)。我猜测问题出在泛型允许键为string或number的任意类型,但我希望所有键是同一类型,为什么这个写法不生效?


解答

错误原因

你遇到的报错和泛型是否限制键为同一类型无关——你的泛型Key extends string | number已经保证了所有键要么全是string类型,要么全是number类型,不会混合出现。

真正的问题在于Record<Key, T>的类型定义:它要求对象必须包含所有Key类型的属性,但你初始化的{}是空对象,完全不满足这个要求。TypeScript的静态类型检查只会看代码的静态结构,它不知道你后续会通过forEach填充对象属性,所以直接判定空对象和Record<Key, T>不兼容。

解决方案

方案1:类型断言(推荐)

既然你明确知道后续会填充对象内容,可以将初始空对象断言为Record<Key, T>,跳过静态类型检查的这个限制:

export function keyBy<Key extends string | number, T>(items: T[] | undefined, extractId: (item: T) => Key): Record<Key, T> {
  // 使用类型断言告诉TS这个对象后续会被正确填充
  const obj = {} as Record<Key, T>;

  if (items) {
    items.forEach(item => {
      const id = extractId(item);
      obj[id] = item;
    });
  }

  return obj;
}

方案2:调整返回类型为Partial

如果你想严格遵循类型定义,可以将返回类型改为Partial<Record<Key, T>>,表示对象可能只包含部分Key类型的属性:

export function keyBy<Key extends string | number, T>(items: T[] | undefined, extractId: (item: T) => Key): Partial<Record<Key, T>> {
  const obj: Partial<Record<Key, T>> = {};

  if (items) {
    items.forEach(item => {
      const id = extractId(item);
      obj[id] = item;
    });
  }

  return obj;
}

不过这种写法的副作用是,调用函数后访问属性时需要处理undefined的情况,如果你确定数组中的键是唯一且会被全部填充,方案1更贴合你的需求。


内容的提问来源于stack exchange,提问作者Kevin Renskers

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:27:30