You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/usage/CodeSplitting.md
+233Lines changed: 233 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -154,6 +154,239 @@ To add a new reducer, one can now call `store.reducerManager.add("asyncState", a
154
154
155
155
To remove a reducer, one can now call `store.reducerManager.remove("asyncState")`
156
156
157
+
## Redux Toolkit
158
+
159
+
Redux Toolkit 2.0 includes some utilities designed to simplify code splitting with reducers and middleware, including solid Typescript support (a common challenge with lazy loaded reducers and middleware).
160
+
161
+
### `combineSlices`
162
+
163
+
The [`combineSlices`](https://redux-toolkit.js.org/api/combineSlices) utility is designed to allow for easy reducer injection. It also supercedes `combineReducers`, in that it can be used to combine multiple slices and reducers into one root reducer.
164
+
165
+
At setup it accepts a set of slices and reducer maps, and returns a reducer instance with attached methods for injection. **At least one reducer is required at setup, as with `combineReducers`.**
166
+
167
+
:::note
168
+
169
+
A "slice" for `combineSlices` is typically created with `createSlice`, but can be any "slice-like" object with `reducerPath` and `reducer` properties (meaning RTK Query API instances are also compatible).
170
+
171
+
```ts
172
+
const withUserReducer =rootReducer.inject({
173
+
reducerPath: 'user',
174
+
reducer: userReducer
175
+
})
176
+
177
+
const withApiReducer =rootReducer.inject(fooApi)
178
+
```
179
+
180
+
For simplicity, this `{ reducerPath, reducer }` shape will be described in these docs as a "slice".
181
+
182
+
:::
183
+
184
+
Slices will be mounted at their `reducerPath`, and items from reducer map objects will be mounted under their respective key.
Be careful to avoid naming collision - later keys will overwrite earlier ones, but Typescript won't be able to account for this.
203
+
204
+
:::
205
+
206
+
#### Slice injection
207
+
208
+
To inject a slice, you should call `rootReducer.inject(slice)` on the reducer instance returned from `combineSlices`. This will inject the slice under its `reducerPath` into the set of reducers, and return an instance of the combined reducer typed to know that the slice has been injected.
209
+
210
+
Alternatively, you can call `slice.injectInto(rootReducer)`, which returns an instance of the slice which is aware it's been injected. You may even want to do both, as each call returns something useful, and `combineSlices` allows injection of the same reducer instance at the same `reducerPath` without issue.
One key difference between typical reducer injection and `combineSlice`'s "meta-reducer" approach is that `replaceReducer` is never called for `combineSlice`. The reducer instance passed to the store doesn't change.
218
+
219
+
A consequence of this is that no action is dispatched when a slice is injected, and therefore the injected slice's state doesn't show in state immediately. The state will only show in the store's state when an action is dispatched.
220
+
221
+
However, to avoid selectors having to account for possibly `undefined` state, `combineSlices` includes some useful [selector utilities](#selector-utilities).
222
+
223
+
#### Declaring lazy loaded slices
224
+
225
+
In order for lazy loaded slices to show up in the inferred state type, a `withLazyLoadedSlices` helper is provided. This allows you to declare slices you intend to later inject, so they can show up as optional in the state type.
226
+
227
+
To completely avoid importing the lazy slice into the combined reducer's file, module augmentation can be used.
As well as `inject`, the combined reducer instance has a `.selector` method which can be used to wrap selectors. It wraps the state object in a `Proxy`, and provides an initial state for any reducers which have been injected but haven't appeared in state yet.
278
+
279
+
The result of calling `inject` is typed to know that the injected slice will always be defined when the selector is called.
280
+
281
+
```ts
282
+
const selectCounterValue = (state:RootState) =>state.counter?.value// number | undefined
`combineSlices` is designed so that the slice is injected as soon as it's needed (i.e. a selector or action is imported from a component that's been loaded in).
302
+
303
+
This means that the typical usage will look something along the lines of the below.
0 commit comments