https://artsy.github.io/blog/2018/11/21/conditional-types-in-typescript/
This year TypeScript gained a new feature that punches far above its weight.
Working through our (enormous) backlog of unsorted TypeScript "Suggestions" and it's remarkable how many of them are solved by conditional types.
Conditional types probably aren't something you'll write every day, but you might end up using them indirectly all the time. That's because they're great for 'plumbing' or 'framework' code, for dealing with API boundaries and other behind-the-scenes kinda stuff. So, dear reader, read on! It's always good to learn how the sausage is made. Then you can make sausage of your own.
Typewurst! 🌭
Note: This is a straightforward adaptation of a 35-minute presentation given at Futurice London's TypeScript Night meetup, and therefore provides more context than an ordinary blog post might. I hope a lot of that context is interesting and useful even for seasoned TypeScript developers. If you'd prefer a no-frills experience, check out the TypeScript 2.8 Release notes .
Here's some plain JavaScript
Reading the code, it's clear to a human that the .toUpperCase()
method call is safe. We can tell that whenever a string is passed in to process
, a string will be returned.
But notice that we could also pass something like null
into the function, in which case null
would be returned. Then calling .toUpperCase()
on the result would be an error.
Let's add basic types to this function so we can let TypeScript worry about whether we are using it safely or not.
Seems sensible. What happens if we try to use it like before?
TypeScript complains because it thinks that the result of process("foo")
might be null
, even though we clever humans know that it won't be. It can't figure out the runtime semantics of the function on its own.
One way of helping TS understand the function better is to use 'overloading'. Overloading involves providing multiple type signatures for a single function, and letting TypeScript figure out which one to use in any given context.
Here we've said that if we pass a string
, it returns a string
, and if we pass null
, it returns null
. (The any
type is ignored but still needs to be there for some reason 🤷️*)*
That works nicely:
But there's another use case that doesn't work:
TypeScript won't let us pass something that is of type string | null
because it's not smart enough to collapse the overloaded signatures when that's possible. So we can either add yet another overload signature for the string | null
case, or we can be like (╯°□°)╯︵ ┻━┻ and switch to using conditional types.