如何使用包含通用参数的通用约束?

时间:2021-03-17 04:36:19

标签: typescript typescript-generics

我一直被这个问题的变体所困扰。假设出于某种原因,我想定义一个函数式 forEach,它采用扩展 Iterable 的任何类型,并返回扩展 Iterable相同 类型。 (我知道 Array.prototype.forEach 有一个 undefined 回报,我想写的版本是不同的。)

例如,forEach([1,2,3], x => whatever(x)) 的类型将是 number[]不是 Iterable<number>

就我所知:

export function forEach <T, ITER extends Iterable<T>> (
  iter: ITER,
  f: (val: T, index: number, iter: ITER) => unknown
): ITER {
  let i = 0;
  for (const x of iter) {
    f(x, i, iter);
    i++;
  }
  return iter;
}

到目前为止一切都很好,但是当我尝试使用它时,我遇到了类型问题:

const result = forEach([1, 2, 3], (x, i, iter) => {
  console.log(x * 2);
});

result 这里是 number[],根据需要,但 x 参数是 unknown 而不是 number,当我尝试乘法时编译器会抱怨它由两个。我对 infer 不屑一顾,但我只是把事情弄得一团糟。

1 个答案:

答案 0 :(得分:2)

为什么 x 被推断为 unknown

在泛型参数替换发生时(即调用函数时),仔细检查编译器推断出的实际函数签名永远不会有什么坏处。以下是您的情况:

function forEach<unknown, number[]>(iter: number[], f: (val: unknown, index: number, iter: number[]) => unknown): number[]

编译器能够确定 ITERnumber[],因为类型信息存在于您作为第一个参数传入的数组文字中。但是 T 的类型只用作回调中 val 参数的类型。由于您传入的回调未明确键入,因此 x 的类型是未知的,因此 x 被推断为 unknown

做什么

解决方案的功劳完全归功于 Linda Paiste,所以我只会提供一些解释。它的核心是以下实用程序,它允许我们通过推断值的类型并返回它来获取迭代器产生的值的类型:

type Iteratee<T extends Iterable<any>> = T extends Iterable<infer I> ? I : never;

剩下的只是将迭代器类型传递给泛型类型参数并相应地更改 val 的类型:

export function forEach <ITER extends Iterable<any>> (
  iter: ITER,
  f: (val: Iteratee<ITER>, index: number, iter: ITER) => unknown
): ITER {
  let i = 0;
  for (const x of iter) {
    f(x, i, iter);
    i++;
  }
  return iter;
}

Playground


附注:在寻找替代解决方案时,我考虑使用索引访问类型来最终提取 IteratorReturnResultvalue 属性类型,但目前已知的符号不能用于索引类型(参见 this issue)使该方法不可行。

有一个 pull request 修复了这个问题,因此从技术上讲可以解开该值,但是由于 ReturnType 实用程序本身使用 infer,因此结果与 Linda 的相比没有任何好处版本。

相关问题