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

如何根据值类型限制泛型Key?TypeScript groupBy类型约束问题

TypeScript 泛型约束问题:限制groupBy的键对应值为string/number类型

我需要实现一个groupBy函数,要求泛型Key对应的Item[Key]必须是string或number类型,但当前两种实现都存在问题:

第一种实现的错误

在Record<Item[Key], Item>处报错:Type does not satisfy the constraint string | number | symbol,代码如下:

function groupBy<
 Item, 
 Key extends keyof { [T in keyof Item]: Item[T] extends PropertyKey ? Item[T] : never}
>(items: Item[], key: Key): Record<Item[Key], Item>{
 const groups = {} as Record<Item[Key], Item>;
 for(const item of items){
   groups[item[key]] = {
     ...groups[item[key]],
     ...item
   };
 }
 return groups;
}

第二种实现的错误

调用时传入'name'作为第二个参数会报错:Object literal may only specify known properties, and 'id' does not exist in type 'Record<"name", PropertyKey>',代码如下:

function groupBy<
  Item extends Record<Key, PropertyKey>,
  Key extends keyof Item
>(items: Item[], key: Key): Record<Item[Key], Item>{
  const groups = {} as Record<Item[Key], Item>;
  for(const item of items){
    groups[item[key]] = {
      ...groups[item[key]],
      ...item
    };
  }
  return groups;
}

预期效果

调用groupBy时,传入'isAlive'应触发类型错误,传入'id'、'name'、'age'应正常运行:

groupBy([
  {id: 1, name: 'Marc', isAlive: false },
  {id: 1, age: 21 },
], 'isAlive'); // 应该触发类型错误


groupBy([
  {id: 1, name: 'Marc', isAlive: false },
  {id: 1, age: 21 },
], 'id'); // 正常运行

groupBy([
  {id: 1, name: 'Marc', isAlive: false },
  {id: 1, age: 21 },
], 'name'); // 正常运行

groupBy([
  {id: 1, name: 'Marc', isAlive: false },
  {id: 1, age: 21 },
], 'age'); // 正常运行

正确实现方案

可以通过自定义条件类型筛选出符合要求的键,同时确保TypeScript能正确推断Item[Key]的类型:

方案1:直接在参数上添加条件约束

function groupBy<Item, Key extends keyof Item>(
  items: Item[],
  key: Item[Key] extends PropertyKey ? Key : never
): Record<Extract<Item[Key], PropertyKey>, Item> {
  const groups = {} as Record<Extract<Item[Key], PropertyKey>, Item>;
  for (const item of items) {
    const groupKey = item[key] as Extract<Item[Key], PropertyKey>;
    groups[groupKey] = {
      ...groups[groupKey],
      ...item
    };
  }
  return groups;
}

方案2:提前定义筛选键的类型

// 定义类型:筛选出对象中值为PropertyKey的键
type PropertyKeyKeys<T> = {
  [K in keyof T]: T[K] extends PropertyKey ? K : never
}[keyof T];

function groupBy<Item, Key extends PropertyKeyKeys<Item>>(
  items: Item[],
  key: Key
): Record<Item[Key], Item> {
  const groups = {} as Record<Item[Key], Item>;
  for (const item of items) {
    groups[item[key]] = {
      ...groups[item[key]],
      ...item
    };
  }
  return groups;
}

错误原因分析

  • 第一种实现:虽然用映射类型筛选了键,但TypeScript无法自动关联Key和Item[Key]必须是PropertyKey的约束,导致Record的键类型不满足要求。
  • 第二种实现:泛型顺序错误,Item extends Record<Key, PropertyKey>会先推断Key,再强制Item符合该约束,但传入的数组对象包含额外属性,超出了Record<Key, PropertyKey>的范围,因此报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:01:18