类型“T[K]”不满足约束“string |” 数量 | 象征'

Vis*_*hal 3 typescript

我正在创建一个简单的函数来从打字稿中的数组中删除重复项。

我知道有很多不同的方法可以实现该目标,但我的最终目标是了解类型的工作原理,因此我不需要与现有代码不同的解决方案。

我的伪代码:

  • 函数将接受对象数组和对象键作为参数
  • 对象数组将转换为字典,仅包含唯一的条目
  • 从对象构造数组并返回它。

使用 JavaScript 的工作代码示例:

function removeDuplicates(arr, propName) {
  const newArr = [];
  const lookup = {};

  for (let i in arr) {
    lookup[arr[i][propName]] = arr[i];
  }

  for (let i in lookup) {
    newArr.push(lookup[i]);
  }

  return newArr;
}
Run Code Online (Sandbox Code Playgroud)

打字稿代码(抛出类型错误)

我正在尝试将该函数转换为 Typescript,但仍停留在声明查找变量的类型上。

这是我的打字稿代码:

function removeDuplicates<T, K extends keyof T>(arr: T[], propName: K) {
  const newArr: T[] = [];
  const lookup: Partial<Record<T[K], T>> = {};
                               ^^^^ here is the error

  for (let i in arr) {
    lookup[arr[i][propName]] = arr[i];
  }

  for (let i in lookup) {
    newArr.push(lookup[i]);
  }

  return newArr;
}
Run Code Online (Sandbox Code Playgroud)

错误:

Type 'T[K]' does not satisfy the constraint 'string | number | symbol'
Run Code Online (Sandbox Code Playgroud)

我知道为什么我会收到错误。我收到错误,因为 object.key 的值可以是任何值。但在我的用例中,我想限制开发人员仅传入值为 string | 的键。数字。但我不知道如何在打字稿中做到这一点。

jca*_*alz 6

正如您所说,您收到错误是因为 whileK限制为数组元素 type 中的键T,但没有任何内容表明该键处的属性T[K]本身是类似键的:

function removeDuplicates<T, K extends keyof T>(arr: T[], propName: K) {
    const lookup: Partial<Record<T[K], T>> = {}; // error!
}

removeDuplicatesOops([{ a: new Date(), b: "hey" }], "a"); // no error
Run Code Online (Sandbox Code Playgroud)

在 TypeScript 中,如果类型可分配给 ,则该类型是“类似键”的string | number | symbol,并为其指定了一个方便的别名 PropertyKey

我们不能T[K] 直接约束,但我们可以约束K和/或T使其T[K]受到有效约束。在编译器意识到这是可接受的情况下获得所需行为的最简单方法Partial<Record<T[K], T>>是进行约束T,以便 in 键处的属性类型K必须可分配给PropertyKey

function removeDuplicates<T extends Record<K, PropertyKey>, K extends keyof T>(
  arr: T[], propName: K) {
    const lookup: Partial<Record<T[K], T>> = {};
    // okay
}

removeDuplicates([{ a: new Date(), b: "hey" }], "a");
// ---------------> ~
// Type 'Date' is not assignable to type 'PropertyKey'.
Run Code Online (Sandbox Code Playgroud)

T是的,对和 的约束K是循环的,但是是以允许的方式(尽管在这种情况下避免循环警告有时很棘手)。请注意,错误出现在参数的键上arr,而不是propName参数本身。如果您确实需要后者,那么您可以K进一步限制,例如

function removeDuplicates<
    T extends Record<K, PropertyKey>,
    K extends keyof { [P in keyof T as T[P] extends PropertyKey ? P : never]: any }
>(arr: T[], propName: K) {
    const lookup: Partial<Record<T[K], T>> = {};
    // okay
}

removeDuplicates([{ a: new Date(), b: "hey" }], "a"); // error!
// -------------------------------------------> ~~~
//Argument of type '"a"' is not assignable to parameter of type '"b"'.
Run Code Online (Sandbox Code Playgroud)

但这可能比您想要的更复杂(并且要解释该K约束是如何工作的,我会花很远的时间)。

Playground 代码链接