Map over a union type
Sometimes you may need want to type an object which has several different optional keys, but all with the same type. We can map over a union type to accomplish this instead of chaining |
in an object type.
I start with some code that looks like this
Let's start improving this. First I will make a FruitCount
type out of my fruitCounts
object, then I'm going to make a new type called NewSingleFruitCount
.
What this is doing is its taking each key in FruitCounts
and setting its type to an empty object. So when I use it, TypeScript will be expecting a shape like this.
But, I want the properties to be numbers.
As you can see, if I just do that, I still haven't quite got a union type. I get this odd nested structure, and I will still have to have every possible fruit present.
So, what I'm going to do is map over our NewSingleFruitCount
type to get rid of those parent keys, and fully create my union type.
And with that, I can now assign our singleFruitCount
how I'd like.
Transcript
I've got a puzzle for you. I've got three letters at the top here that are all expressed as a union. This letter can be either one of A, B, or C. I want to create a type helper to remove one of the letters. How do I do that? You think of, when something is an object or something is an array, you can usually map over it. There's some kind of iterator you can use.
What's the iterator when it comes to unions? How do I map over each member of the union? It turns out that TypeScript does this automatically, and this what's called distributivity in TypeScript. The way this works is we can actually treat these letters as though they were one thing, as though they were, for instance, just C, let's say.
The way we're going to do that is we're just going to check T type. We're going to check if T type extends C, and if not, we're going to return never, because never means that it can never be C. We're trying to remove it from union. When you add never to a union, it just removes itself, basically.
Then, if it doesn't extend C, we're going to return T type. Now, while without C, it's just A or B. Now, that's unexpected, because you would think that T type, C is part of these letters. You would think, "OK, T type does extend C, because C is included, so let's return never."
No, actually, automatically maps over each member of the union when it's doing a conditional type check like this. This means that we can return different members of the union or manipulate them. We can actually just return D, for instance. Now, while without C is A, B, or D.
This is pretty exciting, because it means that you can really tear into unions and do lots of really smart things with them.
Mapping over a union type can feel tricky to conceptualise. But actually, TypeScript does it all for you - using Distributive Conditional Types.
Here, we create RemoveC
- a type helper to remove c
from a union of letters.
More Tips
Derive a union type from an object
Use 'in' operator to transform a union to another union
Decode URL search params at the type level with ts-toolbelt
Use function overloads and generics to type a compose function
Use 'extends' keyword to narrow the value of a generic
Write your own 'PropsFrom' helper to extract props from any React component
Create your own 'objectKeys' function using generics and the 'keyof' operator
Use generics in React to make dynamic and flexible components
Create a 'key remover' function which can process any generic object
Throw detailed error messages for type checks
Use deep partials to help with mocking an entity
Create autocomplete helper which allows for arbitrary values
Turn a module into a type
Use 'declare global' to allow types to cross module boundaries
Use generics to dynamically specify the number, and type, of arguments to functions
Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig
Know when to use generics
Assign local variables to default generic slots to dry up your code and improve performance
Use assertion functions inside classes
Get a TypeScript package ready for release to NPM in under 2 minutes
Understand how TypeScript infers literal types
Ensure that all call sites must be given value
Access deeper parts of objects and arrays
Use infer in combination with string literals to manipulate keys of objects
Compare function overloads and generics
Understand assignability in TypeScript
Avoid unexpected behavior of React’s useState
Transcript
I've got a puzzle for you. I've got three letters at the top here that are all expressed as a union. This letter can be either one of A, B, or C. I want to create a type helper to remove one of the letters. How do I do that? You think of, when something is an object or something is an array, you can usually map over it. There's some kind of iterator you can use.
What's the iterator when it comes to unions? How do I map over each member of the union? It turns out that TypeScript does this automatically, and this what's called distributivity in TypeScript. The way this works is we can actually treat these letters as though they were one thing, as though they were, for instance, just C, let's say.
The way we're going to do that is we're just going to check T type. We're going to check if T type extends C, and if not, we're going to return never, because never means that it can never be C. We're trying to remove it from union. When you add never to a union, it just removes itself, basically.
Then, if it doesn't extend C, we're going to return T type. Now, while without C, it's just A or B. Now, that's unexpected, because you would think that T type, C is part of these letters. You would think, "OK, T type does extend C, because C is included, so let's return never."
No, actually, automatically maps over each member of the union when it's doing a conditional type check like this. This means that we can return different members of the union or manipulate them. We can actually just return D, for instance. Now, while without C is A, B, or D.
This is pretty exciting, because it means that you can really tear into unions and do lots of really smart things with them.