如何理解复杂的 TypeScript 泛型类型?
How to understand complicated typescript generic types?
我正在检查 RxJS api 文档。我总是在理解上面写的奇怪语法时遇到问题,如下所示:
combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>
我想了解这种语法并进一步自己编写。
所以,如果有人能解释一下上面例子中发生的事情,那将会很有帮助。
function combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>
让我们分解一下。
好的,这是一个函数
function combineLatest(args): Result
它接受一些参数并且return是Result
类型的结果
但它也是一种特殊的函数,称为 generic 函数。
泛型的目的是在成员之间提供有意义的类型约束。
function combineLatest<T>(args): Result
/\
now it's a generic function
这里的T
是一个类型变量,它允许我们捕获用户提供的类型,以便我们以后可以使用该信息。例如,我们可以使用 T
作为 return 类型:
function combineLatest<T>(args): T
这样调用如下:
combineLatest<string>(someParams) // returns `string`
将为我们提供 string
类型的结果,因为我们明确地将 T
设置为 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 具有 string
的 name
属性。
现在让我们回到最初的声明:
combineLatest<O extends ObservableInput<any>, R>(...): Observable<R>
我们可以看到这个函数使用了两个类型变量:
O
类型变量,代表 Observable。它被限制为 ObservableInput<any>
类型
R
代表结果
combineLatest
函数的结果应该是 Observable
类型 R
。例如,我们可以通过调用此函数来强制使用该类型:
combineLatest<any, MyClass>(...) // returns Observable<MyClass>
现在让我们看看这个函数可以接受的参数:
...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]
我们可以在这里看到 rest parameters 运算符。让我们简化上面的表达式,想象一下:
...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>
和returns类型的参数R
类型变量类型。请注意,此 returned 类型可用于推断 combineLatest
函数的 return 类型。
或可以是SchedulerLike
接口类型。
我认为如果将它们拆分成小块,理解 TypeScript 类型声明应该没有任何问题。
我正在检查 RxJS api 文档。我总是在理解上面写的奇怪语法时遇到问题,如下所示:
combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>
我想了解这种语法并进一步自己编写。
所以,如果有人能解释一下上面例子中发生的事情,那将会很有帮助。
function combineLatest<O extends ObservableInput<any>, R>(...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]): Observable<R>
让我们分解一下。
好的,这是一个函数
function combineLatest(args): Result
它接受一些参数并且return是Result
但它也是一种特殊的函数,称为 generic 函数。 泛型的目的是在成员之间提供有意义的类型约束。
function combineLatest<T>(args): Result
/\
now it's a generic function
这里的T
是一个类型变量,它允许我们捕获用户提供的类型,以便我们以后可以使用该信息。例如,我们可以使用 T
作为 return 类型:
function combineLatest<T>(args): T
这样调用如下:
combineLatest<string>(someParams) // returns `string`
将为我们提供 string
类型的结果,因为我们明确地将 T
设置为 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 具有 string
的 name
属性。
现在让我们回到最初的声明:
combineLatest<O extends ObservableInput<any>, R>(...): Observable<R>
我们可以看到这个函数使用了两个类型变量:
O
类型变量,代表 Observable。它被限制为ObservableInput<any>
类型
R
代表结果
combineLatest
函数的结果应该是 Observable
类型 R
。例如,我们可以通过调用此函数来强制使用该类型:
combineLatest<any, MyClass>(...) // returns Observable<MyClass>
现在让我们看看这个函数可以接受的参数:
...observables: (O | ((...values: ObservedValueOf<O>[]) => R) | SchedulerLike)[]
我们可以在这里看到 rest parameters 运算符。让我们简化上面的表达式,想象一下:
...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>
和returns类型的参数R
类型变量类型。请注意,此 returned 类型可用于推断combineLatest
函数的 return 类型。或可以是
SchedulerLike
接口类型。
我认为如果将它们拆分成小块,理解 TypeScript 类型声明应该没有任何问题。