我有一个包含两个泛型数组的泛型抽象类:

export abstract class DataProvider<V, T extends {token:string}> {
  inputA: T[] = []; 
  result: V[] = [];
}

我想提供一个函数,您可以在其中传递一个属性名称,该名称应该出现在TV中(并且以某种方式也应该具有正确的类型). 然后,该函数应该在两个数组中找到具有该属性名和相同值的项,并检索令牌并返回一条消息.

这就是我得到的结果:

 public generateMessagesByAttribute<K extends keyof V & keyof T>(attributeName: K): SuccessMessage[] {
  const successMessages: SuccessMessage[] = [];
  this.result.forEach(item => {
    const index = this.inputA.findIndex(input => input[attributeName] === item[attributeName]); // here is the error
    if (index !== -1) {
      successMessages.push({
        taskToken: this.inputA[index].token,
        output: JSON.stringify(item),
      });
    }
  });
  return successMessages;
}

但是,我收到此错误: The comparison appears to be unintentional because because the types U[K] and V[K] have no overlaps个 这是我不明白的,因为我清楚地说明了K extends keyof U & keyof V.

此外,这看起来不是很安全,因为即使属性名可以出现在两个数组中,它们也可以有不同的类型.我怎么才能把这件事也插手呢?

TSPlayground

[更新]一个建议的解决方案是将inputA的类型扩展到(T&V)[].然而,这意味着inputA将包含TV的所有props ,这不是我想要的.然而,我可以以某种方式使inputA的类型更具体,在这个意义上,它必须有一个属性,必须在TV. 但是我不确定当这个属性在类的构造函数中传递时,因此在运行时,这是否可能.

推荐答案

您所遇到的错误是因为,在generateMessagesByAttribute()的实现中,input确实是T类型,itemV类型,而编译器不知道T[K]V[K]是否可以相互比较.如果类型在某种程度上"相关",则TypeScrip只允许input[k] === item[k].解决此问题的最简单方法是在比较之前将inputitem加宽到(T | V):

input[attributeName] === (item as T | V)[attributeName] // okay

还有其他更重量级的方法,例如将this扩大到类似DataProvider<T | V, T>的东西,这将产生input从一开始就是T | V类型的效果:

const thiz: DataProvider<V | T, T> = this; // widen
thiz.result.forEach(item => {
  const index = this.inputA.findIndex(input =>
    input[attributeName] === item[attributeName] // okay
  );
  if (index !== -1) {
    successMessages.push({
      taskToken: this.inputA[index].token,
      output: JSON.stringify(item),
    });
  }
});

或者,您可以通过指定this parameter:将此约束直接放在this上,这样,除非可以适当地加宽对象,否则甚至不能调用该函数:

public generateMessagesByAttribute<K extends keyof T & keyof U>(
  this: DataProvider<V | T, T>,
  attributeName: K
): any[] {
  const successMessages: any[] = [];
  this.result.forEach(item => {
    const index = this.inputA.findIndex(input =>
      input[attributeName] === item[attributeName] // okay
    );
    if (index !== -1) {
      successMessages.push({
        taskToken: this.inputA[index].token,
        output: JSON.stringify(item),
      });
    }
  });
  return successMessages;
}

其中任何一个都应该奏效,其他可能也是如此.


但这并不一定能解决全部问题;即使implementation没有编译器错误,您也可能希望防止callers对键调用generateMessagesByAttribute(),除非这些键类型至少在某种程度上彼此兼容.为此,您不仅可以从Kkeyof T & keyof V(相当于keyof (T | V)),而且可以只到T[K]V[K]重叠的子集.也就是说,其中T[K] & V[K]不是never:

type OverlappingKeys<T, U> =
  { [K in keyof (T | U)]: T[K] & U[K] extends never ? never : K }[keyof (T | U)]
    

你可以看到它是如何工作的:

interface Vee {
  a: string;
  b: number;
  c: string;
  d: "abc";
}
interface Tee {
  a: string;
  b: boolean;
  c: "abc";
  d: string;
  token: string;
}
type VeeTeeOverlap = OverlappingKeys<Tee, Vee>;
// type VeeTeeOverlap = "a" | "c" | "d"

declare const dp: DataProvider<Vee, Tee>;
dp.generateMessagesByAttribute("a"); // okay, both string
dp.generateMessagesByAttribute("token"); // error
dp.generateMessagesByAttribute("b"); // error, no overlap between boolean and number
dp.generateMessagesByAttribute("c"); // okay, overlapping types
dp.generateMessagesByAttribute("d"); // okay, overlapping types

看上go 不错.在上面的例子中,"b"是不可接受的,因为你会把numberboolean做比较.

Playground link to code

Typescript相关问答推荐

错误:note_modules/@types/note/globals.d.ts:72:13 -错误TS 2403:后续变量声明必须具有相同的类型

对深度对象键/路径进行适当的智能感知和类型判断,作为函数参数,而不会触发递归类型限制器

具有动态键的泛型类型

状态更新后未触发特定元素的Reaction CSS转换

学习Angular :无法读取未定义的属性(正在读取推送)

嵌套对象的类型

具有自引用属性的接口

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

将具有Readonly数组属性的对象接口转换为数组

React router 6(数据路由)使用数据重定向

有没有可能创建一种类型,强制在TypeScrip中返回`tyPeof`语句?

从非文字数组(object.keys和array.map)派生联合类型

tailwind css未被应用-react

在构建Angular 应用程序时,有没有办法通过CLI传递参数?

如何静态键入返回数组1到n的函数

如何在Deno中获取Base64图像的宽/高?

如何实现允许扩展泛型函数参数的类型

覆盖高阶组件中的 prop 时的泛型推理

剧作家测试未加载本地网址

如何确定剧作家中给定定位器的值?