We define two bounded contexts: Redux and the (view-) Model. This separates responsibilities into two realms (threads):
UI and Model.
In Command Query Responsibility Segregation (CQRS) terms, this app pattern uses mostly events and few commands. Many commands are actually created inside the user's head, see Tomas Weiss' article. This results in DOM-events (click, etc.), which are handled by the application. It is probably useful to keep this in mind when designing a UI, since commands can be rejected (unlike events). Rejected commands (eg. bad request) can potentially lower the user experience.
Redux uses Event Sourcing but the model can use any pattern. So this pattern uses only few action types. See Oliver Libutzki's article. This is useful for simpler programming of the Model's logic.
The render update only requires the reducer to handle generic state update events, since Immer abstracts away the immutable updates. This allows to handle most of the logic with the easy structural programming style (using async/await
).
There is no need to handle async logic with Thunks, Sagas etc. Actions are queued and passed to a single update function (kind of an asynchronous reducer) in a Pull Stream pattern.
export const updateState = async (state, message, context) => {
if (message.increment) {
state.count += 1;
}
};
Redux Saga is useful for:
- coordination between the UI and logic realms
- action cancellation and other complex event-based features
Immer allows to pass state efficiently between UI and Model inside the Web Worker, see Surma's article. IE11 support for Web Workers and bundling is hard, it throws a security error.
- app example is WIP
- actually use Web Workers (optionally per module)