This is an interesting way to do this. However a much simpler solution is to simply use a tagged/discriminated union. Where you add something like a `type` property to each subtype, set to a different string literal type that describes what it is. Typescript will treat a union of these types the same way as your `OneOf` type does, where once you've selected what subtype it is by specifying a value for `type`, the compiler won't let you over-specify properties. That being said, this could still be useful if the messages come from an outside system and do not contain a `type` property. This will let you type them for your code while still getting the extra bit of type safety. And overall a good exercise to teach some of the more complicated features of TypeScript, to really showcase the kind of power it has.
For messages from an outside system, I find it's a good practice to have a data transformation layer to do some runtime guard statements / error handling, and I might as well add a type 'tag' while I'm at it then. That is a lot more robust and traceable than using the external service directly and trusting its implementation doesn't suddenly change based on a type you defined at some point. 'Type safety' only exists during transpilation. Trusting it to handle runtime dependencies has caused many headaches. Agreed that it was a nice showcase and exercise, though I definitely prefer the simple solution in almost all cases.
Yeah I can appreciate that this is a neat trick, but it seems like a solution that's just too complicated to actually use. This kind of type chenanigans seems like a good way to ensure many people on your team will have a hard time knowing/debugging what's going on. I'm very skeptical about this whole "programming inside the type system". It's another layer of code to maintain, another source of bugs, another level of complexity. At some point you just have to write code and check if it is working correctly. Sometimes debugging an issue is much faster and easier than writing a system to (try to) prevent it.
@@Niosus these are actually very basic type definitions that everyone who has done TS for more than a week should be able to handle. I agree that there are different ways, especially if you want to combine it with runtime checks, but this is very far from being a "chenanigan".
Watching advanced TS like this is like watching Van Gogh paint Starry Night. It's difficult to wrap our heads around the process, so we just say AMAZING, copy-paste the code and use the final product 😂
I was literally having this issue the other day .... the timing of this video and popping up in my feed.... perfect! Awesome to see more advanced typescript videos!
The example of "you can doesn't mean you should". In case if you have 1 extra key it's fine. What if you'll have multiple? What if they'll overlap? Object unions are not magic, it gets out hand really quickly. Also object construction is only one side of the coin, you have to think about type guarding when you'll consume the data. Just add a discriminant property for any message source that has different types, save yourself some headache.
I find that in general the TS type system has this problem. So many ways of doing things, rather than a number of core types that describe common scenarios.
@@arnerademackerthis is because typescript has the impossible task of trying to support everything anybody has ever used js for, but at least it makes the simplest things easy to write types for
That's because types are only as good as the programmer using them. It doesn't matter how good your types are if another programmer cannot see how they function at a glance.
What a beast content! Like it, clear examples, clear explanations, thanks for the great content! Moreover, it is something I have to deal with such scenarios in my 9-5 codebase and this OneOf is a great utility type to be equipped with!
It's very nice to have someone explain some beginner TypeScript expressions in such a clear and practical way! This is not only good if someone wants to start learning TS, it's also a great reference.
This is anything but "beginner" TypeScript. The beginner (and often professional) solution is to create a discriminating property, add it to your data during processing (e.g. after parsing a web request response body into json) and leave it at that. No need for all this magic.
@@arnerademacker it is actually very basic. Everyone with a little TS knowledge should be able to understand and write such definitions, otherwise I wouldn’t consider the person as a TS developer. The video wasn’t about solving this specific problem, which sometimes (but far from always) could be solved with a discriminating union. The video was about exploring a specific type pattern based on a known problem.
just a quick update/fix i would add, instead of using MergeTypes, it just can be TypesArray[number] to generate a union of every element of the array. I really like your content! I hope u keep doing creazy ts stuff
It‘s called WiTT. I‘ve created the extension for Jetbrains products: plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit/analytics
This is wild! I don’t think I fully grasped that recursion was possible within the type system to this degree. Wherever possible, though, I would probably opt for a discriminated union before pulling out any of these tricks. This seems more like the sort of thing you’d want to know as a library author rather than for everyday use.
I was curious what the error message said about the incompatible types. Is it helpful for debugging? With the simple one of it seemed to highlight the bad property, but the real one didn’t highlight the bad properties.
I’m also wondering what the error is. The problem with the simple one is that it only highlighted one of the properties. Either property could be removed to make it work.
The downside to these complicated mapped types and inferred properties is error messages and even the inline type definitions in editors are just so obtuse and hard to decipher.
This is neat, but it doesn't offer the same type safety as a descrinated untion with the extranous properties declared as {prop?:never}. For instance in your code this is valid const message: MessageTypesArray = { id: '1', timestamp: 1, imgPath: 'path', url: undefined, text: undefined } One would expect url & text to throw a TS error of undefined is not assignable to type never, but we get no errors.
Very useful thanks, do you think it's possible to extend the functionality in a way that typescript would be able to flag the exact field in the object? Asking as I have a case with some crazy dynamic unions built using generics and the error message from typescript is usually super long to a point where I can't work out which fields are to blame.
I definitely learned something new today, thanks! Might be worth mentioning that the use of a discriminated union, i.e. adding an explicit `type` property for each variant would also help (but it would be a lot more boring of course 😅)
It‘s called „WiTT“. Its a plugin I‘ve written for Jetbrains: plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit
Yes absolutely. But this should be okay as long as we don‘t have a deep recursion. Also typescript 5.6 just added a huge speed improvement for vscode to prioritize the code you are currently seeing in your ide. But absolutely. Always depends 😁
Thank you so much, I really needed this for a project I am working on, now I can remove my 15 unions type😅 I just have question, in the video I saw you using "// ^?" to show the type of that variable, is that a built in feature inside WebStorm or is it a plugin.
It‘s called „WiTT“. Its‘s a plugin I‘ve created. You can download it at the market place: plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit
The TypeScript team will have to build this in because I'm not explaining this to my teamlead and there's no way to check whether that code harbours any network requests and can cause a security vulnerability to our systems.
First of all, this is a very simple type definition that everyone who has basic TS knowledge should be able to understand. Second, how should something that is purely at build time cause network requests or even vulnerabilities, this sounds as if you should first of all get a basic understanding of TypeScript and of what the words actually mean that you just wrote...
@@gro967 A "simple type definition" is "A | B" or "A & B". Something I would consider medium is a layer of abstraction, say "Omit | Omit". This is an advanced definition, and you should treat it as such or one day it's gonna come and bite you. Secondly, TS doesn't "cause" anything. But treating a network response differently because you applied the wrong type for example allows attackers to inject properties that you end up using because you incorrectly assume your request should have that property. People making incorrect assumptions about how code works is probably the single largest contributor to vulnerabilities. This is why to this day we still refer to KISS: Keep It Simple, Silly.
Haven't tested this very thoroughly, but I think we could get a slightly better resolved type by doing something like: type ExclusiveVariantUnion = (T & { [K in keyof Omit]?: never; }) | (U & { [K in keyof Omit]?: never; }); type OneOf = T extends [infer U] ? U : T extends [infer Head, ...infer Tail] ? ExclusiveVariantUnion : never;
Personally I prefer this over the recursive formulation since its more modular and avoids the need to declare the array type since it can be applied to the union directly: type UnionToIntersection = (U extends any ? (x: U) => 0 : never) extends (x: infer I) => 0 ? I : never; type OneOf = TypesUnion extends any ? OnlyFirst : never; type OnlyFirst = F & { [Key in keyof Omit]?: never }; type Example = OneOf;
@@gro967 I would rephrase that: Generics is the _only_ place where something like this is necessary. The moment you have clearly defined types you have enough control to just MyLibraryType & { type: 'MyLibraryType' } and then set the property yourself whenever you're retrieving data using that library. It's JS after all, object composition is meant to be a core feature that since the inception of type script people think is evil.
Very good and advanced typescript information. I just wish you spend more time explaining the original problem to solve, because this is a pattern I use and I'm not sure if I am suffering this issue. Does this happen if you tag each member ?
Very cool! :) I wonder how having lots of such types will do to IDE performance. Will exporting a OneOf be cached property by typescript, or will it run that recursion ever time you import that OneOf?
Niceee! another great video Question, I always wanted to create something like this, but my reason was because what bothers me the most about conditional unions is that ts intelisense will still show properties of the other conditions when you're clearly already inside one I saw in your video still happens to you, ImgUrl and URL are still showing in intellisense! I usually have a top level property like "type" which serves to switch between types, and even when starting an object with that type set, it still shows properties of the other types
Yes this is really a problem I think can‘t be solved as the ts-server still allows the property but only with the type „never“. So it shows it. But will think about it if there might be a way to 👍
didn't work out for me. Need to try to dive deep into the mechanism to understand why it doesn't work in my case. Does ti require specific version of typescript?
This is awesome, thank you! I’m curious what the resolved type of “message” becomes when using this. For example, if it uses only the properties of TextMessage, does TS recognize message as TextMessage?
@@Typed-Rocks Nice one, thanks for the reply and making such good content. Really hope you continue to gain more subs and your channel grows, you deserve it.
Could have used this the other day for multiple different forms of data, most likely will go back to it Little harder of test types and seem to work also. Mostly contrived but I am 99% sure this can happen. type Default = {id: number, something: string} type A = Default & {text: string, error: string}; type B = Default & {text: string, status: number}; type C = Default & {status: number, other: any[]}; type MessageTypesArray = OneOf; const message: MessageTypesArray = { id: 1, something: 'test', // text: 'test', // error: 'test', status: 132, other: [] }
Can we somehow see to what type it was computed to? Let's say I have Person & Car and it somehow shows that it was computed to Person? I guess it can be achieved only with some ide plugin?
TLDR: KISS This type of a metaprogramming is fine when it used only for teaching\learning purpose, but more optimal for this case should be just a class for each type that implement BaseMessage as an interface. More over, there is not just problem with a union in Message type, bc this is a union, no more, no less, and it works as it should; But your manual creation of an object that should satisfy any of that types makes your problem, and this is a main purpose of usage of a classes here, or constructor function if to be specific. Or if you hate classes by whole your soul, you can use a Builder pattern for that
Very interesting videos! But isnt it better to use `unknown` instead of `any`? And yeah in this example it doesnt make any difference. But sometimes you might miss that your type checking isnt working because of any
is creating these types unnecessarily heavy in compile time or work time(as in would it slow the IDE to have such complex types all over)? if we do need this, wouldn't a discriminated union be better? baking it into the codebase instead of just the types?
@@Typed-Rocks i wouldn't know how to measure that, if you can do a vid on that it'd be great. make me feel more comfortable in introducing such things to an actual app. btw, undoubtedly its cool that typescript can do this, and that you can make it do this, just how realistic is it to use such things and open the codebase up to letting people do such heavy types all over, yknow? if its very minimal impact then i'd like to know that
Sadly this will not work. Because we cannot loop over a union and figure out what belongs together. Because for typescript this union is just one large type concatenated together.
At this point I would rather not use TS at all 😂 All I wanted is basical intellisense and not this monstrosity that fallen into type extremism allowing you do same in one thousand ways and most of them wrong
I didn't actually watch the video, the premise is wrong. "ExactlyOne" of "A/B" is XOR, not OR. XOR is a regular CPU operation, compare with that logic gate instead.
I thought I knew TS well enough, until I found this channel
Exactly 😂😂
This is an interesting way to do this. However a much simpler solution is to simply use a tagged/discriminated union. Where you add something like a `type` property to each subtype, set to a different string literal type that describes what it is. Typescript will treat a union of these types the same way as your `OneOf` type does, where once you've selected what subtype it is by specifying a value for `type`, the compiler won't let you over-specify properties.
That being said, this could still be useful if the messages come from an outside system and do not contain a `type` property. This will let you type them for your code while still getting the extra bit of type safety. And overall a good exercise to teach some of the more complicated features of TypeScript, to really showcase the kind of power it has.
Exactly, oneOf is useful when you dont want to use a discriminated one because you dont have access to it. Well said 👍
For messages from an outside system, I find it's a good practice to have a data transformation layer to do some runtime guard statements / error handling, and I might as well add a type 'tag' while I'm at it then. That is a lot more robust and traceable than using the external service directly and trusting its implementation doesn't suddenly change based on a type you defined at some point. 'Type safety' only exists during transpilation. Trusting it to handle runtime dependencies has caused many headaches.
Agreed that it was a nice showcase and exercise, though I definitely prefer the simple solution in almost all cases.
Exactly my way of thinking
Yeah I can appreciate that this is a neat trick, but it seems like a solution that's just too complicated to actually use. This kind of type chenanigans seems like a good way to ensure many people on your team will have a hard time knowing/debugging what's going on.
I'm very skeptical about this whole "programming inside the type system". It's another layer of code to maintain, another source of bugs, another level of complexity.
At some point you just have to write code and check if it is working correctly. Sometimes debugging an issue is much faster and easier than writing a system to (try to) prevent it.
@@Niosus these are actually very basic type definitions that everyone who has done TS for more than a week should be able to handle. I agree that there are different ways, especially if you want to combine it with runtime checks, but this is very far from being a "chenanigan".
Typescript: has very complex and useful type system
Library Developers: we will just use 'any'
Laziness :)
Or, they simply don't export their types, like NgRx.
Watching advanced TS like this is like watching Van Gogh paint Starry Night. It's difficult to wrap our heads around the process, so we just say AMAZING, copy-paste the code and use the final product 😂
Thank you 😁 That‘s why I always add the GitHub link. 🤘
I was literally having this issue the other day .... the timing of this video and popping up in my feed.... perfect!
Awesome to see more advanced typescript videos!
The example of "you can doesn't mean you should". In case if you have 1 extra key it's fine. What if you'll have multiple? What if they'll overlap? Object unions are not magic, it gets out hand really quickly. Also object construction is only one side of the coin, you have to think about type guarding when you'll consume the data.
Just add a discriminant property for any message source that has different types, save yourself some headache.
I find that in general the TS type system has this problem. So many ways of doing things, rather than a number of core types that describe common scenarios.
@@arnerademackerthis is because typescript has the impossible task of trying to support everything anybody has ever used js for, but at least it makes the simplest things easy to write types for
I'm glad to find yet another typescript enthusiast. I love doing such typy things, and everyone at my workplace sees me like an alien.
That's because types are only as good as the programmer using them. It doesn't matter how good your types are if another programmer cannot see how they function at a glance.
What a beast content! Like it, clear examples, clear explanations, thanks for the great content! Moreover, it is something I have to deal with such scenarios in my 9-5 codebase and this OneOf is a great utility type to be equipped with!
A couple of days ago I saw video. And today, I just encountered the union type problem, and this video just saved me. Thanks for such a great tip 👍
Great to hear 👍
It's very nice to have someone explain some beginner TypeScript expressions in such a clear and practical way!
This is not only good if someone wants to start learning TS, it's also a great reference.
This is anything but "beginner" TypeScript. The beginner (and often professional) solution is to create a discriminating property, add it to your data during processing (e.g. after parsing a web request response body into json) and leave it at that. No need for all this magic.
Thank you :). And of course there are other possibilities to solve that. 👍
@@arnerademacker it is actually very basic. Everyone with a little TS knowledge should be able to understand and write such definitions, otherwise I wouldn’t consider the person as a TS developer.
The video wasn’t about solving this specific problem, which sometimes (but far from always) could be solved with a discriminating union. The video was about exploring a specific type pattern based on a known problem.
@@gro967 I don’t want to know what your complex custom typescript types look like 👀
@@arnerademacker Something like a CamelCase type:
type IsGap = Uppercase extends Lowercase ? true : false
type CamelCase = S extends Lowercase
? S extends `${infer F}_${infer RF}${infer R}`
? RF extends '_'
? `${F}_${CamelCase}`
: `${F}${IsGap extends true ? `_${RF}` : Uppercase}${CamelCase}`
: S
: CamelCase
just a quick update/fix i would add, instead of using MergeTypes, it just can be TypesArray[number] to generate a union of every element of the array.
I really like your content! I hope u keep doing creazy ts stuff
Thanks, I will check it out. 🙏
This is incredible. You explained well but it's pretty complicated. Hope I'll find a usecase where I can use it
Yes this is something hard to wrap your head around
By the way what is the extension/plugin you used in the video that shows the inline type information by `// ^?`?
It‘s called WiTT. I‘ve created the extension for Jetbrains products: plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit/analytics
Wow! This is awesome! I really love your explanations! They are fantastic. I am looking forward to more videos from you 😊
This is wild! I don’t think I fully grasped that recursion was possible within the type system to this degree. Wherever possible, though, I would probably opt for a discriminated union before pulling out any of these tricks. This seems more like the sort of thing you’d want to know as a library author rather than for everyday use.
I was curious what the error message said about the incompatible types. Is it helpful for debugging? With the simple one of it seemed to highlight the bad property, but the real one didn’t highlight the bad properties.
I’m also wondering what the error is. The problem with the simple one is that it only highlighted one of the properties. Either property could be removed to make it work.
Sadly this is a restriction of typescript
Through the video I thought "well, that is interesting, I can understand what's going oooooooooWTFDIDHEJUSTDO", it was magical.
The downside to these complicated mapped types and inferred properties is error messages and even the inline type definitions in editors are just so obtuse and hard to decipher.
Absolutely. You always have to think if you really want to add something like this in.
This is neat, but it doesn't offer the same type safety as a descrinated untion with the extranous properties declared as {prop?:never}. For instance in your code this is valid
const message: MessageTypesArray = {
id: '1',
timestamp: 1,
imgPath: 'path',
url: undefined,
text: undefined
}
One would expect url & text to throw a TS error of undefined is not assignable to type never, but we get no errors.
Interesting. Will have to look into it. Maybe a tsconfig setting is needed. Thanks 👍
Very useful thanks, do you think it's possible to extend the functionality in a way that typescript would be able to flag the exact field in the object? Asking as I have a case with some crazy dynamic unions built using generics and the error message from typescript is usually super long to a point where I can't work out which fields are to blame.
@@aro_matt It should somehow. I will look into it and tag you on this comment when I find somethinf. :)
I definitely learned something new today, thanks! Might be worth mentioning that the use of a discriminated union, i.e. adding an explicit `type` property for each variant would also help (but it would be a lot more boring of course 😅)
That would also he a nice video 👍
I actually only use tagged unions in typescript, it's the only way to take advantage of type inference and guards
This wizard is really underated 😂
I see you used "^?" to resolve a type. What tool provides this? this seems INCREDIBLY useful
It‘s called „WiTT“. Its a plugin I‘ve written for Jetbrains: plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit
The VSCode extension is called Twoslash Query Comments
I would also like to know
there is also an extension for vs code that does this. it's called two slash something, it's written by orta
You're seriously a hidden gem, you're on the same level as Matt Pocock, I'd say that you're even better than him !
Thank you very much. It is a huge honor to hear that. :)
Great work brooo keep it up so that i can upskill my self
that's cool, but don't abuse it or your typecheck will start taking forever :)
Yes absolutely. But this should be okay as long as we don‘t have a deep recursion. Also typescript 5.6 just added a huge speed improvement for vscode to prioritize the code you are currently seeing in your ide. But absolutely. Always depends 😁
Thank you so much, I really needed this for a project I am working on, now I can remove my 15 unions type😅
I just have question, in the video I saw you using "// ^?" to show the type of that variable, is that a built in feature inside WebStorm or is it a plugin.
It‘s called „WiTT“. Its‘s a plugin I‘ve created. You can download it at the market place:
plugins.jetbrains.com/plugin/23294-witt--typescript-type-hints-like-in-the-ts-playground/edit
It's called Twoslash, and i'ts a "markup format for TypeScript code". You can usually find plugins for it on all editors
I’m curious of how the error looks like
Would have been nice to see the error messages, since now they are not located where the error occurs but elsewhere.
Yes, that‘s an issue. You are absolutely right
The TypeScript team will have to build this in because I'm not explaining this to my teamlead and there's no way to check whether that code harbours any network requests and can cause a security vulnerability to our systems.
First of all, this is a very simple type definition that everyone who has basic TS knowledge should be able to understand.
Second, how should something that is purely at build time cause network requests or even vulnerabilities, this sounds as if you should first of all get a basic understanding of TypeScript and of what the words actually mean that you just wrote...
@@gro967 A "simple type definition" is "A | B" or "A & B". Something I would consider medium is a layer of abstraction, say "Omit | Omit". This is an advanced definition, and you should treat it as such or one day it's gonna come and bite you.
Secondly, TS doesn't "cause" anything. But treating a network response differently because you applied the wrong type for example allows attackers to inject properties that you end up using because you incorrectly assume your request should have that property.
People making incorrect assumptions about how code works is probably the single largest contributor to vulnerabilities. This is why to this day we still refer to KISS: Keep It Simple, Silly.
Haven't tested this very thoroughly, but I think we could get a slightly better resolved type by doing something like:
type ExclusiveVariantUnion
= (T & { [K in keyof Omit]?: never; })
| (U & { [K in keyof Omit]?: never; });
type OneOf
= T extends [infer U]
? U
: T extends [infer Head, ...infer Tail]
? ExclusiveVariantUnion
: never;
Personally I prefer this over the recursive formulation since its more modular and avoids the need to declare the array type since it can be applied to the union directly:
type UnionToIntersection = (U extends any ? (x: U) => 0 : never) extends (x: infer I) => 0 ? I : never;
type OneOf = TypesUnion extends any ? OnlyFirst : never;
type OnlyFirst = F & { [Key in keyof Omit]?: never };
type Example = OneOf;
Nice approach, but you could also add a type in all extended types as a string literal and this will be explicit based on that type.
Absolutely. This is more for types which we don‘t have control over 👍
That is hardly possible for generics or if you need to handle libraries from external packages, which is normally the case.
@@gro967 I would rephrase that: Generics is the _only_ place where something like this is necessary. The moment you have clearly defined types you have enough control to just MyLibraryType & { type: 'MyLibraryType' } and then set the property yourself whenever you're retrieving data using that library. It's JS after all, object composition is meant to be a core feature that since the inception of type script people think is evil.
@@arnerademacker you are probably right, all other cases I thought about are also more or less generics at some point 😅
Very good and advanced typescript information. I just wish you spend more time explaining the original problem to solve, because this is a pattern I use and I'm not sure if I am suffering this issue. Does this happen if you tag each member ?
I will create an own video about this problem. Thanks for your idea 🙏
It is interesting trick! I am always used discriminated union for such task.
Also a great way 🙏
Very cool! :) I wonder how having lots of such types will do to IDE performance. Will exporting a OneOf be cached property by typescript, or will it run that recursion ever time you import that OneOf?
Great question. It gets cached. The typescript type checker does a really great job in caching things which did not change 👍
Honestly `const obj: {foo: string} | {bar: string} = {foo: "foo", bar: "bar"}` not erroring feels like a bug in my head.
Could you also solve this by not exporting the Message type (keeping it private)?
Yes that should work too
Niceee! another great video
Question, I always wanted to create something like this, but my reason was because what bothers me the most about conditional unions is that ts intelisense will still show properties of the other conditions when you're clearly already inside one
I saw in your video still happens to you, ImgUrl and URL are still showing in intellisense!
I usually have a top level property like "type" which serves to switch between types, and even when starting an object with that type set, it still shows properties of the other types
Yes this is really a problem I think can‘t be solved as the ts-server still allows the property but only with the type „never“. So it shows it. But will think about it if there might be a way to 👍
didn't work out for me. Need to try to dive deep into the mechanism to understand why it doesn't work in my case. Does ti require specific version of typescript?
It should just work. Maybe set the tsconfig to strict: true?
@@Typed-Rocks I created pull request with the example of types where offered solution doesn't work
What happens if you mistype a property name that no sub-type contains, such that it conforms to BaseMessage but nothing else?
Then you will get an error that this property does not exist at all 👍
This is awesome, thank you! I’m curious what the resolved type of “message” becomes when using this. For example, if it uses only the properties of TextMessage, does TS recognize message as TextMessage?
Yes, it does. If you try to update the message object it only allows the TextMessage ones
Could we have added a tag/discriminating field or does it not achieve the same thing?
At first glance you could absolutely do that. The only difference would be that you then just need to add it to your new objects
@@Typed-Rocks Nice one, thanks for the reply and making such good content. Really hope you continue to gain more subs and your channel grows, you deserve it.
this is golden!
Could have used this the other day for multiple different forms of data, most likely will go back to it
Little harder of test types and seem to work also. Mostly contrived but I am 99% sure this can happen.
type Default = {id: number, something: string}
type A = Default & {text: string, error: string};
type B = Default & {text: string, status: number};
type C = Default & {status: number, other: any[]};
type MessageTypesArray = OneOf;
const message: MessageTypesArray = {
id: 1,
something: 'test',
// text: 'test',
// error: 'test',
status: 132,
other: []
}
Great to hear that it might be helpful. Thank you 🙏
Hey! How do you get these "Types Programming" skill from? Is there a "Leetcode of Types Programming" or stuff like that?
Great question. Not really. I just try the stuff myself when I think it could work and I have an issue to solve :)
@@Typed-Rocks Sounds good! Followed!
Can we somehow see to what type it was computed to? Let's say I have Person & Car and it somehow shows that it was computed to Person? I guess it can be achieved only with some ide plugin?
Good question. I think you have to use an ide plugin.
What's the extension to print the resolved type via "^?"?
It‘s called „WiTT“. And you can find it on the Jetbrains marketplace
@@Typed-Rocksoh yeah, I did find it eventually before I read your comment. And it's yours, right? Thanks for creating it!
TLDR: KISS
This type of a metaprogramming is fine when it used only for teaching\learning purpose, but more optimal for this case should be just a class for each type that implement BaseMessage as an interface.
More over, there is not just problem with a union in Message type, bc this is a union, no more, no less, and it works as it should; But your manual creation of an object that should satisfy any of that types makes your problem, and this is a main purpose of usage of a classes here, or constructor function if to be specific. Or if you hate classes by whole your soul, you can use a Builder pattern for that
I was indeed wondering if using actual classes and interfaces would not also solve this issue without the complexity. Glad I saw your comment
Is this the same as a "discriminated type"?
It's essentially doing the same thing, just using a different approach.
I wonder how this would affect compile time if used in a large project...
This should not affect it as the recursion is really small. Also the typescript compiler does a great job in optimizing.
Very interesting videos! But isnt it better to use `unknown` instead of `any`? And yeah in this example it doesnt make any difference. But sometimes you might miss that your type checking isnt working because of any
is creating these types unnecessarily heavy in compile time or work time(as in would it slow the IDE to have such complex types all over)?
if we do need this, wouldn't a discriminated union be better? baking it into the codebase instead of just the types?
Yes of course we could also use a discriminated union. But this kind of type I‘ve created is really easy for typescript to do
@@Typed-Rocks i wouldn't know how to measure that, if you can do a vid on that it'd be great. make me feel more comfortable in introducing such things to an actual app.
btw, undoubtedly its cool that typescript can do this, and that you can make it do this, just how realistic is it to use such things and open the codebase up to letting people do such heavy types all over, yknow? if its very minimal impact then i'd like to know that
Could this also be solved with a __tag attribute inside the types?
It can absolutely 👍
Nice one, but can we make it work also without the array notation? Ex: const test: OneOf = ..., rather than const test: OneOf = ...
Sadly this will not work. Because we cannot loop over a union and figure out what belongs together. Because for typescript this union is just one large type concatenated together.
@@Typed-Rocks thanks for answering
@@aleksander5298 Really? then please show me :). I will be glad to learn something new. :)
Thank you!
Sometimes I think that this should be the default behaviour for using Unions. 😅
A builtin OneOf would be amazing!
if you using Head naming for first item then remaining would be a Tail
Tail would be last element
I always thought fixing TS is done by using JS
const simple = { hey, hi };
type Union = keyof typeof simple;
I use enum type property and then just `& (foo | bar | baz)`
WHY?
Why not? 😁
This is magical
I don't like this, it breaks the Liskov substitution principle. Is message of type TextMessage or UrlMessage? It's simply both
My head hurts
good one
Thanks 🙏
I'm genuinely sorry for my language, but that was fucking awesome.
Your language is highly appreciated 😁
At this point I would rather not use TS at all 😂 All I wanted is basical intellisense and not this monstrosity that fallen into type extremism allowing you do same in one thousand ways and most of them wrong
I just do
AorB = { type: 'A', customPropA: string } | { type: 'B', customPropB: string }
Edit: what @Matt23488 also proposed
type gymnastics
Always stay flexible 💪
Dude that’s just XOR types
Bro got a skip button 😭
Good bye discriminated unions
KISS
Literally just use a discriminating union. With interfaces. And it works.
Could you quickly show how? :)
I didn't actually watch the video, the premise is wrong. "ExactlyOne" of "A/B" is XOR, not OR. XOR is a regular CPU operation, compare with that logic gate instead.
As usual with these kinds of solutions the end result is illegible and incredibly difficult to understand.