如何理解复杂的 TypeScript 泛型类型?

10

我在查看RxJS的API文档。我经常遇到理解顶部奇怪语法的问题,例如下面的示例:

combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>

我希望能够理解这种语法,并进一步独立写出它。
因此,如果有人能够解释上面的例子中发生了什么,那将是非常有帮助的。

在这里询问工具、库或外部资源是不被允许的。 - Andreas
1
好的。我也要求上面代码的解释。 - Rakesh Joshi
1个回答

13

function combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>

让我们分解一下。

这是一个函数。

function combineLatest(args): Result

这个函数需要一些参数,并返回一个类型为Result的结果。

但它也是一种特殊的函数,被称为泛型函数。泛型的目的是在成员之间提供有意义的类型约束。

function combineLatest<T>(args): Result
                      /\
           now it's a generic function

T 在这里是一个类型变量,它允许我们捕获用户提供的类型信息,以便我们稍后可以使用该信息。例如,我们可以将 T 用作返回类型:

function combineLatest<T>(args): T

为了使以下调用生效:

combineLatest<string>(someParams) // returns `string`

由于我们明确将 T 设置为 string,因此将给出类型为 string 的结果。

我们还可以将 T 类型变量用于函数的任何参数。

function combineLatest<T>(arg: T): T

现在我们可以使用类型参数推断

combineLatest('someStr') // returns `string`

也就是说,我们告诉编译器根据我们传入的参数类型自动设置T的值。我们传递了一个string,所以得到了一个string
我们可以使用任意数量的类型变量,而且还可以随心所欲地命名它们。
function combineLatest<Type1, Type2, ...>(...)

泛型很棒,有时我们想对传入的参数执行一些操作:

function combineLatest<T>(arg: T): T {
  arg.name = arg.name.toLowerCase(); // Property 'name' does not exist on type 'T'
  return arg;
}

正如您所看到的,编译器无法理解arg的类型。为了解决这个问题,我们可以使用extends运算符来表示对T类型变量的约束:

interface ItemWithName {
  name: string;
}
function combineLatest<T extends ItemWithName>(arg: T): T {
  arg.name = arg.name.toLowerCase();
  return arg;
}

现在编译器知道arg具有字符串的name属性。


现在让我们回到初始声明:

combineLatest<O extends ObservableInput<any>, R>(...): Observable<R>

我们可以看到这个函数使用了两个类型变量:
  • O 类型变量代表 Observable。它被限制为 ObservableInput<any> 类型。
  • R 代表结果。
combineLatest 函数的结果应该是类型为 RObservable。例如,我们可以通过以下方式强制指定类型:
combineLatest<any, MyClass>(...)  // returns Observable<MyClass>

现在让我们来看一下这个函数可以接受哪些参数:
...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]

我们可以在这里看到 剩余参数 运算符。让我们简化上面的表达式,使我们想象一些东西:
...observables: CombinedType[]

这意味着combineLatest函数可以接受零个或多个参数,这些参数最终将被聚合到一个变量observables中。
combineLatest();                 // without parameters
combineLatest(obs1);             // one parameter
combineLatest(obs1, obs2, ..etc) // many parameters

那么这个参数的类型是什么?

           CombinedType

O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike

它可以是:

  • 类型为O的变量,其约束为我们之前讨论过的ObservableInput<any>类型

  • 或者它可以是函数(...values: ObservedValueOf<O>[]) => R,它接受零个或多个ObservedValueOf<O>类型的参数,并返回R类型变量类型。请注意,此返回类型可用于推断combineLatest函数的返回类型。

  • 或者它可以是SchedulerLike接口的类型。


如果您将TypeScript类型声明分解成小块,我认为不应该存在任何理解上的问题。


1
非常感谢 @yurzui。这真的是非常有帮助的解释。 - Rakesh Joshi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接