Become a TypeScript Wizard with my free beginners TypeScript Course: www.totaltypescript.com/tutor... Follow Matt on Twitter / mattpocockuk Join the Discord: mattpocock.com/discord
This is what always makes me feel that i'm doing something wrong like there is another better way to do this. For now I ended up checking type for typescript to be able to extract only one route and not mess my types.
I thought it wouldn't error, I thought the type of that parameter would be "number | string | boolean", it was surprising seeing it to collapse into "never" lol In this case I would give each function a generic parameter so I would be able to actually specify the exact type of that parameter later when executing the function
Hey Matt, thanks for this video. It made me to remember another typescript situation related to union types, I would like to post my question here, may be you will want to elaborate it as another video. type UiComponents = { Accordions: Array; Buttons: Array; } const components: UiComponents = {Accordions: [], Buttons: []} const getComponent = (componentsName: T, id: UiComponents[T][number]['id']) => { return components[componentsName].find(item => item.id === id); //
It should be possible to create an object which has all of the string properties, all of the number properties and all of the boolean properties. But in practice, one of the obstacles would be the + operator, since it doesn’t work outside the context of primitive numbers.
If it didn't that'd be crazy. This seems very obvious. It calls all 3 functions with 1 argument, so naturally that 1 argument has to match all 3 functions signatures
I can't really understand why people think it shouldn't error, just call the correct function for that type... It's clearly inside a loop, it WILL call EACH function with the same parameter... Obviously two of them will error, clear as day
Oh I stumbled upon recently what you are talking about. I used rxjs and wrote some code, like. (isSomething ? of('test') : of(123)).pipe(tap((v) => console.log(v))) And I couldn't use the arguments in the pipe, because the type of the pipe is Observable | Observable.
This made me finally dig into and figure out what the type of a function that DOES support all of the above formats looks like (doing different things, and each call style showing separate refdocs on hover), without having to use the parameter as the union of all in the one and only parameter list: ``` /** does something with a number */ function myFunc(a: number): void; /** does something with a string */ function myFunc(b: string): void; /** does something with a boolean */ function myFunc(c: boolean): void; function myFunc(x: number|string|boolean) {} ``` And to see what the legal types looks like, it turns out to be: ``` type F = typeof myFunc; // ^? // type F = { (a: number): void; (b: string): void; (c: boolean): void; } ```
This gets particularly useful in situations where you have a generic on an optional arg, which typescript is really stingy about allowing you to do with any kind of nice syntax, e g: ``` /** resolves to `value` after `milliseconds`, or `undefined`, if omitted */ export async function delay(milliseconds: number): Promise; export async function delay(milliseconds: number, value: V): Promise; export async function delay( milliseconds: number, value?: V ): Promise { return new Promise((resolve) => setTimeout(() => resolve(value), milliseconds) ); } type T = typeof delay; // ^? type T = { (milliseconds: number): Promise; (milliseconds: number, value: V): Promise; } ```
This if a feature of typescript called function overloading, which appears to be syntactical sugar for typing the function implementation as an object with method overloads for its call signature. I don’t think it’s possible to create a universal and useful utility type for something like defining overloads for a function with optional generic parameters, because you cannot know how many generic arguments the function you are deriving from accepts.
He is just making a demonstration to help us understand typescript behavior. There probably isn’t a practical use case, or at least it would be very niche. You are supposed to assume that you cannot change the type of myFunc because it derives its type from the functions stored within it. You would be introducing bugs into your code just to satisfy the typescript compiler. The only solution besides checking the types of the functions before they are executed is what he showed.
I've checked and adding as const to the function array, doesn't make typescript realise that on index 0 fn will want a number, and on index 1 it's a string. const fns = [ (a: number) => {}, (b: string) => {}, ] as const; fns.map((fn, i) => { fn(i === 0 ? 1 : '1'); }) this kind of makes sense, but we're not on that level yet
const fns = [ (a: number) => {}, (b: string) => {}, ] as const; type Fns = typeof fns; fns.map((fn, i) => { type Fn = Fns[i]; const myFync: Fn = fn; myFync(i === 0 ? 1 : '1'); }) This ain't possible either, without the usage of Generic all hope is lost
That seems like an error in TS, then? It shouldn't make it into a union, it should be string OR number OR boolean, then the code would remain intuitive and you wouldn't have to add verbose code to the function mapper where you have to solve an issue that TS created. Or is there some benefit to this that I'm missing? I can imagine that in a tuple it would be super annoying, too.
I expected it not to error because I thought TS would make func take a parameter which is a union of number | string | boolean and a number satisfies that. 😢
Full expected it to error, and was pleasantly surprised to see it collapse into `never`.
@@HighlyVolkish 😅 Sorry, just interacting with the video and commented my answer.
because the never is the intersection of all bool, string and num
This is what always makes me feel that i'm doing something wrong like there is another better way to do this. For now I ended up checking type for typescript to be able to extract only one route and not mess my types.
I choose "no error" even though I knew typescript **should** error, just because Matt was pretty tricky on the last one!
Same problem as when you try to use function overloading. In implementation, you must indicate types for arguments to much all overloads.
I thought it wouldn't error, I thought the type of that parameter would be "number | string | boolean", it was surprising seeing it to collapse into "never" lol
In this case I would give each function a generic parameter so I would be able to actually specify the exact type of that parameter later when executing the function
I liked this format!
Awesome, Im still starting out with Typescript, this is all still new to me but I am starting to understand a little.
Sometimes it would be valuable to let type coercion work in this cases and you could type the parameter as Parameters[0].
Hey Matt, thanks for this video. It made me to remember another typescript situation related to union types, I would like to post my question here, may be you will want to elaborate it as another video.
type UiComponents = {
Accordions: Array;
Buttons: Array;
}
const components: UiComponents = {Accordions: [], Buttons: []}
const getComponent = (componentsName: T, id: UiComponents[T][number]['id']) => {
return components[componentsName].find(item => item.id === id); //
It should be possible to create an object which has all of the string properties, all of the number properties and all of the boolean properties. But in practice, one of the obstacles would be the + operator, since it doesn’t work outside the context of primitive numbers.
type Cursed = Number & Boolean & String
Sure it will!
If it didn't that'd be crazy. This seems very obvious.
It calls all 3 functions with 1 argument, so naturally that 1 argument has to match all 3 functions signatures
I can't really understand why people think it shouldn't error, just call the correct function for that type... It's clearly inside a loop, it WILL call EACH function with the same parameter... Obviously two of them will error, clear as day
Awesome and useful content
Have you done a video on contravariance yet?
i hope it would, but I think he doesn't. I think the derived type of the argument will be string|boolean|number, so the error checking will pass
Oh I stumbled upon recently what you are talking about. I used rxjs and wrote some code, like.
(isSomething ? of('test') : of(123)).pipe(tap((v) => console.log(v)))
And I couldn't use the arguments in the pipe, because the type of the pipe is Observable | Observable.
"Put your life on the line!..." Haha.
And I chose no, it was kinda obvious for me...
Make more of these type of videos, they are so fun.
Why would one not expect an error there?
I get this question a lot! So figured I'd do a video on it.
This is actually how the UnionToIntersection type works
would setting myFuncs as const and checking the index on the map function make this work somehow?
func is typed as a union of the functions under myFunc so if you did that and compared equality between func and an index of myFunc yes it would work
That it errors was clear to me. But I didn't expect it to become never, but on second thought it makes sense.
This made me finally dig into and figure out what the type of a function that DOES support all of the above formats looks like (doing different things, and each call style showing separate refdocs on hover), without having to use the parameter as the union of all in the one and only parameter list:
```
/** does something with a number */
function myFunc(a: number): void;
/** does something with a string */
function myFunc(b: string): void;
/** does something with a boolean */
function myFunc(c: boolean): void;
function myFunc(x: number|string|boolean) {}
```
And to see what the legal types looks like, it turns out to be:
```
type F = typeof myFunc;
// ^? // type F = {
(a: number): void;
(b: string): void;
(c: boolean): void;
}
```
This gets particularly useful in situations where you have a generic on an optional arg, which typescript is really stingy about allowing you to do with any kind of nice syntax, e g:
```
/** resolves to `value` after `milliseconds`, or `undefined`, if omitted */
export async function delay(milliseconds: number): Promise;
export async function delay(milliseconds: number, value: V): Promise;
export async function delay(
milliseconds: number,
value?: V
): Promise {
return new Promise((resolve) =>
setTimeout(() => resolve(value), milliseconds)
);
}
type T = typeof delay;
// ^? type T = {
(milliseconds: number): Promise;
(milliseconds: number, value: V): Promise;
}
```
I'd LOVE a treatise on how to compose types like these with type helpers, as I've drawn a complete blank on how to do that, but find the need.
This if a feature of typescript called function overloading, which appears to be syntactical sugar for typing the function implementation as an object with method overloads for its call signature.
I don’t think it’s possible to create a universal and useful utility type for something like defining overloads for a function with optional generic parameters, because you cannot know how many generic arguments the function you are deriving from accepts.
Yes
yes
I bet my entire existance on a TypeScript error since 123 is neither a string nor boolean.
I dont know what use case that would apply to, but wouldnt it be better to do ` myFunc: ( a : number | string | boolean ) => void; ` ??
He is just making a demonstration to help us understand typescript behavior. There probably isn’t a practical use case, or at least it would be very niche.
You are supposed to assume that you cannot change the type of myFunc because it derives its type from the functions stored within it. You would be introducing bugs into your code just to satisfy the typescript compiler.
The only solution besides checking the types of the functions before they are executed is what he showed.
Yes beause the 123, is a number and doesn't suit the last two possible function declarations of myFuncs elements
I could tell that it should error, I thought and hoped it would error, but did not foresee the `never`.
It is related to the concept of the "string &&number = never" in TypeScript????
Yes!
Maybe semantics, but I would say the functions are being "intersectioned" instead of being "unioned" together 😉
The functions are being unioned together, but the parameter gets intersectioned because of that.
@@mattpocockuk Ah, right! I should have tested in the playground before trying to be a smartypants 😅
Cool now please make a follow up on what to do when you’re in that situation 😂
'as never' probably
I would be pissed if it didn't error.
Would it still error if you would use index, and conditionally give it a proper type?
Yep!
@@mattpocockuk Ok, that I would not have guessed :D, I thought it would've been fine with it.
But what if you add as const?
I've checked and adding as const to the function array, doesn't make typescript realise that on index 0 fn will want a number, and on index 1 it's a string.
const fns = [
(a: number) => {},
(b: string) => {},
] as const;
fns.map((fn, i) => {
fn(i === 0 ? 1 : '1');
})
this kind of makes sense, but we're not on that level yet
const fns = [
(a: number) => {},
(b: string) => {},
] as const;
type Fns = typeof fns;
fns.map((fn, i) => {
type Fn = Fns[i];
const myFync: Fn = fn;
myFync(i === 0 ? 1 : '1');
})
This ain't possible either, without the usage of Generic all hope is lost
Can I get to see the tsconfig please???
Assume strict true, that's basically it.
That seems like an error in TS, then? It shouldn't make it into a union, it should be string OR number OR boolean, then the code would remain intuitive and you wouldn't have to add verbose code to the function mapper where you have to solve an issue that TS created. Or is there some benefit to this that I'm missing?
I can imagine that in a tuple it would be super annoying, too.
It's not OR, it's the intersection (AND) of those types.
@@BraedenSmith Yes I know, and I'm saying it should be different,
This would call all of these methods with the same argument, so that singular argument would have to be valid for all 3 functions, which is impossible
No!!!
You solve this case by pattern matching.
Will error
So the only reason to this video is to hype up for the TS 5.2 version?
?
No, this behaviour is the same for both versions.
My sanity depends on this being yes.
What 😮
Can I say also that the code in the thumbnail will error too because it's a const without initializer ? x)
Imagine it's a declare const, but I didn't want to add 'declare' in the thumbnail.
no
I expected it not to error because I thought TS would make func take a parameter which is a union of number | string | boolean and a number satisfies that.
😢
typescript doesn’t allow for implicit type coercion, which is actually a feature of javascript.
no error
Yes it will throw error because type number is not assignable to type string
Still confused 😅😐
It will error.
Yes it will error, life on the line.
No error
yes but i have a feeling this is a trick
Am i the only one totally confused as to why people love how bizarre js/ts is, like why would you do this?
I hope it will error
Yeah function params are covariant. And this is the basis for the famous UnionToIntersection type helper. 😁
Yes
yes
yes
yes