我正在try 创建一个泛型TypeScript函数,该函数递归地通过树 struct 查找具有特定id值的项.为了使其可重用,我try 使用泛型,您只需为函数提供顶级、id值以及用于id和子级的属性名称.下面是我的示例课程:

export class Thing{
  name: string;
  id: string;
  children: Thing[];

  constructor(){
    this.name='';
    this.id='';
    this.children=[];
  }
}

以及按id查找项目的方法:

export function GetGenericItemById<T>(
  allItems: T[],
  idField: keyof T,
  childrenField: keyof T,
  id: string
): any {
  const item = allItems.find((x) => x[idField] === id);
  if (item != null) {
    return item;
  }

  const subItems = allItems.flatMap((i) => i[childrenField]);

  if (subItems.length === 0) {
    return undefined;
  }

  return GetGenericItemById(subItems, idField, childrenField, id);
}

然而,在find函数中,它进行比较x[idField] === id,它告诉我This condition will always return 'false' since the types 'T[keyof T]' and 'string' have no overlap..这里的目的是从对象中获取该属性并获取其值.我做错了什么?

我做这件事时没有像这样的泛型:

export function GetGenericItemById(
  allItems: any[],
  idField: string,
  childrenField: string,
  id: string
): any {
  const item = allItems.find((x) => x[idField] === id);
  if (item != null) {
    return item;
  }

  const subItems = allItems.flatMap((i) => i[childrenField]);

  if (subItems.length === 0) {
    return undefined;
  }

  return GetGenericItemById(subItems, idField, childrenField, id);
}

这是可行的,但是你也可以把你想要的任何东西放入idFieldchildrenField属性中,这样你就可以很容易地把它搞砸.最好将这些密钥限制为您正在使用的类型的有效密钥.我以为我在上面的try 是这样做的,但它似乎不一样,因为它给出了那个错误.

EDIT

这里的每个请求都是我打算如何使用它的一个简单示例.假设我有一个简单的数据 struct ,其中包含"things".每个Thing人都有一个id和一个名字,以及其他Thing人的潜在子女.这样地:

const data=[
  {
    name: "thing 1",
    id: "1",
    children: [
      {
        name: "thing 1a",
        id: "1a",
        children: []
      }
    ]
  },
  {
    name: "thing 2",
    id: "2",
    children: [
      {
        name: "thing 2a",
        id: "2a",
        children: []
      }
    ]
  }
];

我的意图是将函数称为这样的函数:

const foundThing = GetGenericItemById<Thing>(topThings, 'id', 'children', '1a');

在本例中,topThings将是一个只包含顶级项(示例数据中的ID 1和ID 2)的集合.基本上,它只是在树下搜索以找到该项目.我希望foundThing是id为"1a"的Thing对象.

EDIT

你真的不应该把<Thing>部分放进go ,因为第一个论点暗示了这一点,但为了清楚起见,我把它放在这里.

推荐答案

问题是,编译器不知道T类型的对象的idField键处的属性值是string,因此它不允许您将其与字符串进行比较.此外,它不知道T类型的对象的childrenField键处的属性是T的数组值,因此它不允许您仅对其调用GetGenericItemById.

为了将这一点传递给编译器,您需要将函数generic设置为idField类型(称为IK),以及childrenField类型(称为CK).然后我们希望constrain T是一个对象类型,在键IK处有string属性,在键CK处有T[]属性.

写这种类型的一种方法是

Record<IK, string> &
Record<CK, T[]> &
Record<string, any> 

它使用the Record<K, V> utility type表示"具有K类型键和V类型值的对象",使用intersections将约束组合在一起.您可以将其视为"具有IKstring属性的对象、andCK属性的对象、andstring属性的对象".最后一位为Record<string, any>是不必要的,但它降低了allItems参数因具有excess properties而被拒绝的可能性.

下面是现在没有错误的函数:

function GetGenericItemById<
  IK extends string,
  CK extends string,
  T extends Record<string, any> & Record<IK, string> & Record<CK, T[]>
>(
  allItems: T[],
  idField: IK,
  childrenField: CK,
  id: string
): T | undefined {
  const item = allItems.find((x) => x[idField] === id); // okay
  if (item != null) {
    return item;
  }

  const subItems = allItems.flatMap((i) => i[childrenField]);

  if (subItems.length === 0) {
    return undefined;
  }

  return GetGenericItemById(subItems, idField, childrenField, id); // okay
}

这些错误消失了,因为现在编译器知道x[idField]stringsubItemsT[].

返回类型已从any更改为T | undefined,因此对GetGenericItemById的调用将具有强类型结果.让我们测试一下:

GetGenericItemById(
  topThings,
  'id',
  'children',
  '1a'
)?.name.toUpperCase(); // THING 1A

看起来不错;编译器接受调用,返回值的类型为Thing | undefined.

Playground link to code

Typescript相关问答推荐

我用相同的Redux—Toolkit查询同时呈现两个不同的组件,但使用不同的参数

单击并移除for循环中的一项也会影响另一项

如何使用函数填充对象?

在嵌套属性中使用Required

在Reaction-Native-ReAnimated中不存在Animated.Value

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

尽管对象的类型已声明,但未解析的变量<;变量

Angular 自定义验证控件未获得表单值

有没有办法防止类型交集绕过联合类型限制?

如何在不违反挂钩规则的情况下动态更改显示内容(带有状态)?

Typescript将键数组映射到属性数组

类型脚本中参数为`T`和`t|unfined`的重载函数

如何使用useSearchParams保持状态

显式推断对象为只读

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?

使用组合 api 和 typescript 验证 vuetify 表单

RTK:useSelector() 与 store.getState()

在 Typescript 中,如何修改使用通用键获得的数组类型的属性?

TypeScript 不扩展泛型并抱怨类型参数不具有可比性

Object.values() 不从联合或记录派生类型