Rematch Code Deep Dive: Part 1 - Comparing Rematch and Redux
![/en/rematch-series-rematch-vs-redux/featured-image.webp /en/rematch-series-rematch-vs-redux/featured-image.webp](/en/rematch-series-rematch-vs-redux/featured-image.webp)
Rematch Code Deep Dive: Part 1 - Comparing Rematch and Redux
After reading Redesigning Redux, this article continues the discussion on the differences between Rematch and Redux. I will implement a simple Counter application using Redux, and then Rematch. For the Redux implementation, asynchronous processing will be handled using redux-thunk or redux-saga. Through this example, I will compare the differences between Rematch and Redux, and explore the advantages of Rematch, such as reduced learning curve and less code. Finally, I will discuss how Rematch wraps Redux to facilitate a smooth transition. This will involve an exploration of Rematch’s code architecture, which I have divided into several parts and will explain in subsequent posts.
1 Counter Example
I will use Redux and Rematch to implement a simple counter application in React, featuring increment, decrement, and asynchronous increment functionalities.
1.1 Redux Implementation
In the pure Redux implementation, there are two approaches to implementing the asynchronous increment functionality: one using redux-thunk and the other using redux-saga.
The directory structure is very simple:
|
|
components/Counter.js
|
|
reducers/index.js
|
|
The code in index.js
varies depending on the asynchronous logic used, as detailed below.
1.1.1 redux-thunk
Click here for the complete code
If asynchronous logic uses redux-thunk, the code in index.js
is as follows:
|
|
After using the thunk middleware, dispatch()
can accept a function as an argument. The redux store then passes dispatch
and getState
as arguments to this function. This way, if the function is asynchronous, it can asynchronously dispatch an action.
1.1.2 redux-saga
Click here for the complete code
If asynchronous logic uses redux-saga, the code in index.js
is as follows:
|
|
Apart from the differences in middleware configuration, unlike redux-thunk which dispatches a function as an action, saga requires defining some asynchronous logic (using saga’s built-in asynchronous APIs). Therefore, a saga.js
is added under the src/reducers
directory:
|
|
Saga uses generator functions to control the asynchronous flow more precisely. The above code exports a default saga function. takeEvery("INCREMENT_ASYNC", incrementAsync)
listens for all actions with action.type
as INCREMENT_ASYNC
, and upon detecting such an action, executes the incrementAsync()
function. This function uses call(fakeAsyncLogic)
to simulate an asynchronous call, followed by put({ type: "INCREMENT" })
to dispatch an action, eventually triggering the reducer to execute.
1.2 Rematch Implementation
Click here for the complete code
In Rematch, there are no separate reducers; the reducer belongs to a data structure called a model, so the directory structure is slightly different (replace reducers
with models
):
|
|
The code in Counter.js
remains unchanged, and the code in models/index.js
is as follows:
|
|
As you can see, we have defined a model named count
, which includes state
, reducers
, and effects
. The state
is data belonging to the model, equivalent to the first parameter of the redux reducer function (or its return value). The reducers
are equivalent to the redux reducer, and the effects
include some side-effect logics (like API calls, etc.).
Finally, the index.js
:
|
|
At this point, the store
is no longer created using Redux’s API createStore
, but instead with Rematch’s init
. Two points to note here:
- Now
store.getState()
returns not a number but{ count: number }
. store.dispatch
is not just a function (maintaining the Redux call method), but also supports calling a reducer or effect in the form ofdispatch.modelName.xxx
. However, it should be noted thataction.type
is now in the form ofmodelName/reducerName
ormodelName/effectName
.
As mentioned before, the smallest unit in Rematch is a model. Therefore, the above changes are also made to accommodate the model format.
2 Rematch vs Redux
From the above example, we can see several differences between the two:
- If using Redux, asynchronous actions require separate middleware, such as thunk or saga. In contrast, Rematch can directly use ES’s
async/await
syntax for asynchronous action dispatch. - Redux does not have the concept of a model. If the state structure is complex, combineReducers can be used to merge different reducers, forming a similar state structure. Rematch natively supports this.
- Redux’s
store.dispatch
is simply a function. However, Rematch retains this function functionality while also providing a way for chainable calls.
Since the example above is quite simple, the differences are not numerous. More differences can be found in Redesigning Redux. Here are two more common differences:
- Redux makes more use of functional programming ideas, such as the
compose
utility function provided for combining store enhancers during store initialization. - Simplified reducers mainly include omitting the definition of
action.type
constants and omitting theswitch/case
branch in the reducer. Therefore, a reducer in a Rematch model is equivalent to acase
branch, with its name acting asaction.type
.
In my opinion, Rematch is superior to Redux in three main aspects:
- More “rational” data structure design: Rematch uses the concept of a model, integrating state, reducer, and effect. This integration is very practical in frontend development, for example, designing different models for different page routes.
- Simpler API design: Redux uses function composition for configuration, which may be confusing at first for developers unfamiliar with functional programming. Rematch, on the other hand, uses object-based configuration options, which are easier to grasp.
- Less code:
- Eliminates the need for a large number of
action.type
constants and branch decisions in Redux. - Native syntax support for asynchronous operations, eliminating the need for middleware. Using saga has a learning curve, and using thunk, with its varied action types, can also be confusing.
- Eliminates the need for a large number of
Additionally, Rematch provides a plugin mechanism. Besides many community-developed plugins, we can also do custom development. The plugins will be detailed in later posts.
3 Rematch Code Structure
We know that Rematch is essentially a wrapper based on Redux, simplifying Redux’s complex syntax:
Rematch is Redux best practices without the boilerplate.
Because of this, it is still fundamentally Redux and does not diminish Redux’s functionality. So, how does Rematch achieve this? How is it designed? I will next explain the core code structure of Rematch based on v1.4.0 (the last version of Rematch v1).
Note: I participated in the update of Rematch v2 and will write a separate post introducing the changes from Rematch v1 to v2. v1 is used here because its logic has not fundamentally changed, and it is easier to read and understand, whereas v2 has significant changes in coding styles.
Let’s first look at the code directory structure of Rematch v1.4.0:
|
|
I have divided the Rematch code into the following components:
Rematch consists of a core and plugins. The core is divided into two parts: the Rematch class and the redux.ts
file. The former is the core source code of Rematch, while the latter mainly contains code for reducer merging, used for creating the Redux store.
The plugin mechanism in Rematch is used to enhance its functionality, with the main code defined in the plugin factory. The Rematch core includes two essential plugins: dispatch and effect. The dispatch plugin enhances the store.dispatch
function, allowing for chainable calls. The effect plugin primarily supports the async/await
asynchronous pattern. In addition to these two plugins, the Rematch team has developed other third-party plugins, such as loading, select, etc., integrating features like asynchronous request loading status and selectors.
Next, I will explain these parts in detail, breaking them down into three posts: Rematch core, plugin factory & core plugins, and 3rd party plugins. After these three posts, I will write two more posts. One will cover the changes from Rematch v1 to v2, and the other will introduce the Rematch type system (the most significant change brought by the v2) as well as some remaining issues and challenges with this type system, which I hope to discuss with you all.
Stay tuned!