Use 'declare global' to allow types to cross module boundaries
Let's talk about globals in TypeScript. Globals in TypeScript can be manipulated and used in really interesting ways.
Here we have a global reducer.
export const todosReducer: GlobalReducer<{ todos: { id: string }[] }> = (
state,
event
) => {
return state
}
And this global reducer has representation of state. We take in a state and have an event, and then we return the state, which is a typical pattern for a reducer.
Problem is, our event is typed as never. Let's fix that. We can access TypeScript's global scope using the declare global
syntax. We'll use that then create a GlobalReducerEvent
interface.
declare global { interface GlobalReducerEvent {} }
In it, we'll create some events
Let's say we have an add to-do event, where we can pass text with the string
type.
declare global {
interface GlobalReducerEvent {
ADD_TODO: { text: string }
}
}
Now we can access that event in our global reducer.
export const todosReducer: GlobalReducer<{ todos: { id: string }[] }> = (
state,
event
) => {
// event.type = 'ADD_TODO'
return state
}
So we've also got another reducer called the user reducer, which is another global reducer. This reducer also now receives the add to-do event type since we used declare global
.
If we want to do the same thing here, then we would just copy and paste the declare global
code over. and we would add some more events to the global scope.
For instance, we might have a log in function and need to support a log-in event, which all reducers can receive. That's usually how it works in something like Redux.
declare global {
interface GlobalReducerEvent {
LOG_IN: { ... }
}
}
export const userReducer: GlobalReducer<{ id: string }> = (
state,
event
) => {
// event.type === 'LOG_IN'
// event.type === 'ADD_TODO'
return state
}
The way that this works is we have a types file. And in it, we declare an empty interface, GlobalReducerEvent
. And then, there's a bit of a clever mapping type, which maps over all of the log in stuff, and then turns it into a union type.
declare global {
interface GlobalReducerEvent {
LOG_IN: {}
}
}
export type GlobalReducer<TState> = (
state: TState,
event: {
[EventType in keyof GlobalReducerEvent]: {
type: EventType
} & GlobalReducerEvent[EventType]
}[keyof GlobalReducerEvent]
) => TState
So here we're just saying EventType in keyof GlobalReducerEvent
. And in that mapping, set the type
to EventType
. And then, we grab anything the user puts in their reducer events, and we append it to or intersect it with the event as well.
And this means that you can use globals. in really cool ways across your application.
Transcript
0:00 Let's talk about globals in TypeScript, because globals in TypeScript can be manipulated and used in really interesting ways. Here, we have a global reducer, and this global reducer, it's got its state here, or its representation of state.
0:15 In fact, this is supposed to be an array of to-dos, because it's to-dos, ID, string, array. Here, we take in a state, we have an event, and then we return the state, which is a typical pattern for a reducer, except our event is typed as never, so let's fix that.
0:31 We can say declare global, which is a way of accessing TypeScript's global scope, and we're going to say interface global reducer event. We're going to pass in some events here. Let's say we have an add to-do event, where we can pass in an ID of string, for instance, to-do, for instance, or text string. That maybe makes more sense.
0:53 Now, our event here has add to-do in it, and we can actually access it saying event.type equals add to-do. We've also got another reducer called the user reducer, which is another global reducer. This also receives now add to-do. If we wanted to do the same here, then we would just copy and paste this over, and we would add some more events to the global scope.
1:19 Now, for instance, if we have a used Lexa or something like that -- so in here, we might have a login function -- and now, we have a login event, which all reducers can receive. That's usually how works in something like Redux. Now, the way that this works is we have a types file in here.
1:38 In here, we just declare an empty interface, global reducer event here. Here, there's a bit of a clever mapping type, which maps over all of the login stuff here, and then turns it not a union type. Here, we're just saying key of global reducer event, so here are the keys.
1:56 We grab the type in there, and then we grab anything the user puts in here, and we append it to or intersect it with the event as well. This means that you can use globals in really cool ways across your application.
Globals in TypeScript?! 🤯
declare global
is a super useful tool for when you want to allow types to cross module boundaries. Here, we create a GlobalReducer
type, where you can add new event types as you create new reducers.
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
Turn a module into a type
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