Turn a module into a type
Let's imagine that we've got a union type that looks like this.
export type Action = "ADD_TODO" | "REMOVE_TODO" | "EDIT_TODO"
This is a common pattern in older Redux applications. Now, we've actually got a constants.ts file which has all of the elements of the union. They're even inferred by TypeScript.
The exports are not actually inferred as strings. They're inferred as the literals "ADD_TODO", "REMOVE_TODO", and "EDIT_TODO".
export const ADD_TODO = "ADD_TODO"
export const REMOVE_TODO = "REMOVE_TODO"
export const EDIT_TODO = "EDIT_TODO"
So there must be a way that we can extract this information and create the union type dynamically. Because otherwise, whenever we add a new element in constants.ts
, we've also got to add it to the new place. It's just not very DRY.
So if we go to types, what we can do is we can say export type ActionModule
. And we're going to use this funky little bits of TypeScript syntax.
export type ActionModule = typeof import("./constants")
You can use any kind of normal import that you would here, and it just grabs the information from constants and creates a new type out of it. So set something to the type of ActionModule
, you would see that it has all of the elements in constants.ts
.
in there. So if we remove one of the exports from constants
, it won't be a part of these types anymore, which is quite clever.
Then we can use the trick we learned before to create a union type out of the keys of ActionModule
. So we can say this:
export type ActionModule = typeof import("./constants")
export type Action = ActionModule[keyof ActionModule]
What that does is it takes the exported keys of the ActionModule
, and it sort of iterates over them. So we end up with ADD_TODO
, REMOVE_TODO
, and EDIT_TODO
which stays in sync with our actual code.
Transcript
0:00 Hello, folks. Let's imagine that we've got a union type that looks like this, where we have add to-do, remove to-do, and edit to-do as part of the union. This is a common pattern in old Redux applications. Now, this action type here, we've actually got a constants.ts file right next to it which has all of the elements of the union.
0:23 They're even inferred by TypeScript, because we have add to-do is actually not inferred as a string, it's inferred as the literal add to-do, same as remove to-do, edit to-do. There must be a way that we can extract this information and create the union type dynamically.
0:38 Otherwise, whenever we add a new element here, we've also got to add it to the new place. It's just not very dry. If we go to types, what we can do is we can say export type, action module, and we're going to use this funky little bit of TypeScript syntax, which is type of import constants.
0:57 You can use any kind of normal import that you would here. It just grabs the information from constants and creates a new type out of it. We can say export constant action module is action module, like this, and you can see that it has all of the elements in there.
1:14 If we removed one of the exports from constants, for instance, like if we just take this off, then it won't be a part of this types anymore, which is quite clever. Now, what we want to do, then, is we want to take this action module, and we want to turn it into the action below, if I just re-add this export.
1:33 The way that we can do that is we can use a trick that we've done before, which is the take the values of an object and turn them into a union, which we just do like this. We say action module, and then key of action module. What that does is it takes the keys of the action module, the exported keys, and it iterates over them, so we end up with add to-do, remove to-do, edit to-do, that will stay in sync with our actual code.
Want to turn a module into a type? You can use typeof import('./')
to grab the type of any module, even third-party ones.
Here, we create a type from a constants.ts
file, then map over the values to create a union.
More Tips
Type Predicates
1 min
TypeScript 5.1 Beta is OUT!
2 mins
How to Name your Types
4 mins
Don't use return types, unless...
4 mins
TypeScript 5.0 Beta Deep Dive
6 mins
Conform a Derived Type Without Losing Its Literal Values
1 min
Avoid unexpected behavior of React’s useState
1 min
Understand assignability in TypeScript
2 mins
Compare function overloads and generics
1 min
Use infer in combination with string literals to manipulate keys of objects
1 min
Access deeper parts of objects and arrays
1 min
Ensure that all call sites must be given value
1 min
Understand how TypeScript infers literal types
1 min
Get a TypeScript package ready for release to NPM in under 2 minutes
1 min
Use assertion functions inside classes
1 min
Assign local variables to default generic slots to dry up your code and improve performance
2 mins
Know when to use generics
2 mins
Map over a union type
1 min
Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig
1 min
Use generics to dynamically specify the number, and type, of arguments to functions
1 min
Use 'declare global' to allow types to cross module boundaries
2 mins
Create autocomplete helper which allows for arbitrary values
2 mins
Use deep partials to help with mocking an entity
1 min
Throw detailed error messages for type checks
1 min
Create a 'key remover' function which can process any generic object
1 min
Use generics in React to make dynamic and flexible components
1 min
Create your own 'objectKeys' function using generics and the 'keyof' operator
1 min
Write your own 'PropsFrom' helper to extract props from any React component
1 min
Use 'extends' keyword to narrow the value of a generic
1 min
Use function overloads and generics to type a compose function
2 mins
Decode URL search params at the type level with ts-toolbelt
2 mins
Use 'in' operator to transform a union to another union
2 mins
Derive a union type from an object
2 mins