Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to understand complicated typescript generic types?

I was checking RxJS api documents. I always face problem in understanding the weird syntax written on the top, Which is like below as an example:

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

I wanted to understand this kind of syntax and further write it by my own.

So, It will be great help if anyone explain what's going on in above example.

like image 951
Rakesh Joshi Avatar asked Feb 04 '23 18:02

Rakesh Joshi


1 Answers

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

Let's break it down.

Okay, that's a function

function combineLatest(args): Result

which takes some parameters and returns a result of type Result

But it's also a special kind of functions called generic functions. The purpose of generic is to provide meaningful type constraints between members.

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

The T here is a type variable which allows us to capture the type the user provides, so that we can use that information later. For example, we can use T as the return type:

function combineLatest<T>(args): T

so that the following call:

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

will give us the result of type string since we explicitly set T to be string.

We can also use that T type variable for any of arguments of the function.

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

And now we can use type argument inference:

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

That is, we tells the compiler to set the value of T for us automatically based on the type of the argument we pass in. We've passed a string so that we've got a string.

We can use as many type variables as we want. And also we can call them whatever we want.

function combineLatest<Type1, Type2, ...>(...)

Generics are great and sometimes we want to make some actions with the parameters we've passed in:

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

As you can see the compiler can't understand what type of arg. To remedy this we can denote the constraint for our T type variable with the help of extends operator:

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

Now the compiler knows that arg has the name property of string.


Now let's come back to our initial declaration:

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

We can see here that this function uses two type variables:

  • O type variable which stands for Observable. It is constrained to be type of ObservableInput<any>
  • R stands for Result

The result of the combineLatest function should be Observable of type R. For example we can force that type by calling this function like:

combineLatest<any, MyClass>(...)  // returns Observable<MyClass>

Now let's look at the parameters this function can take:

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

We can see the rest parameters operator here. Let's simpify the expression above so we imagine something like:

...observables: CombinedType[]

This means that the combineLatest function can take zero and more arguments which will be later gathered together into one variable observables.

combineLatest();                 // without parameters
combineLatest(obs1);             // one parameter
combineLatest(obs1, obs2, ..etc) // many parameters

So what's the type of this parameter

           CombinedType

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

It can be:

  • the type of O type variable which is constrained to be type of ObservableInput<any> we discussed earlier

  • or it can be function (...values: ObservedValueOf<O>[]) => R which takes zero or more parameters of type ObservedValueOf<O> and returns R type variable type. Note that this returned type can be used to infer the return type of the combineLatest function.

  • or it can be type of SchedulerLike interface.


I think there shouldn't be any problem with understanding TypeScript type declarations if you split them into small pieces.

like image 180
yurzui Avatar answered Feb 07 '23 12:02

yurzui