danvk / effective-typescript

Effective TypeScript 2nd Edition: 83 Specific Ways to Improve Your TypeScript
https://effectivetypescript.com
Other
1.51k stars 227 forks source link

Generic Tips Part 1: Use Classes and Currying to create new inference sites #22

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

Generic Tips Part 1: Use Classes and Currying to create new inference sites

Effective TypeScript: Generic Tips Part 1: Use Classes and Currying to create new inference sites

https://effectivetypescript.com/2020/12/04/gentips-1-curry/

arvabalazs commented 2 years ago

Hi

First of really good post!

I just wonder the first declaration

declare function getUrl<API, Path extends keyof API>(path: Path, params: ExtractRouteParams<Path>);

is equivalent with the following (assuming we do not need the Path type anymore directly)

declare function getUrl<API>(path: keyof API, params: ExtractRouteParams<typeof path>);

It would spare the whole hussle of all-or-nothing type interference (at least in this example). You may add this for sake of completeness.

Thank you for the answer. Disclaimer: I am pretty new to TS with strong C++ background. Sorry if the question is stupid

arvabalazs commented 2 years ago

Similar question: this would be also the very same?

function getUrl<API, Path=keyof API>(path: Path, params: ExtractRouteParams<Path>)
danvk commented 2 years ago

@arvabalazs interesting question. Neither of those declarations is quite the same. For this declaration:

declare function getUrl<API>(path: keyof API, params: ExtractRouteParams<typeof path>): string;

is that typeof path is just keyof API, i.e. the union of all possible routes. It will never have a more specific type. In the example in the post, it will be "/users" | "/users/:userId". Because of the way that conditional types work in TypeScript (they distribute over unions), the net result is that the type of params can match any of the possible paths, not just the one you called getUrl with. Here's a playground link showing how this allows incorrect usage.

The second declaration has the same problem:

declare function getUrl<API, Path=keyof API>(path: Path, params: ExtractRouteParams<Path>): string;

In this case, if you omit the second type parameter (Path) then it's always set to keyof API. It won't be inferred as something more specific based on the value of path when you call getUrl. Here's a playground link showing this.

Until there's movement on https://github.com/microsoft/TypeScript/issues/10571, you really do need to use the techniques described in this article to get partial inference.

arvabalazs commented 2 years ago

Thank you @dankv! Your explanation helps me a lot to build up my mental model about this language.