Rematch Code Deep Dive: Part 5 - Rematch v2, a big step

This is a part of the Rematch Code Deep Dive Series, focusing on the transition to Rematch V2.
From this article onwards, we will delve into Rematch v2.
1 Join the Rematch
Before discussing v2, let me share my experience of joining Rematch.
Looking back, I never expected to become a contributor to Rematch, a well-known open-source project. Initially, I used Rematch for a new project at my company. During its use, I found its TypeScript (TS) compatibility to be poor, especially the dispatch
, which was not at all compatible with TS. I raised an issue and submitted a corresponding PR, but Rematch did not address it promptly. Consequently, I submitted a PR in our internal scaffold, creating a file rematch.d.ts
to override Rematch’s native type definitions and correct most types.
About a week later, Rematch maintainers contacted me via email, asking for help with Rematch’s type issues. I readily agreed, which led to my involvement in the iteration of v2.
The last version of Rematch v1 and the example code version for the previous articles, v1.4.0, was released on 2020-02-23. After that, the Rematch team began iterating on v2 (by then, Rematch’s founder was no longer involved with Rematch), releasing the first pre-release version @rematch/core@2.0.0-next.1 on 2020-07-30.
I joined Rematch on 2020-08-17 (writing this blog on 2021-08-17, a year has flown by) and facilitated the pre-release of @rematch/core@2.0.0-next.2 on 2020-08-19. After 10 pre-release versions, we finally released the official v2 on 2021-01-31, nearly a year in the making.
That’s a brief history of Rematch v2 and my reasons for joining Rematch. In this article, I’ll mainly introduce some changes brought about by the upgrade to Rematch v2. Most of my involvement was in TS type compatibility, so some of these changes are directly related to my contributions, while others are based on ideas I gathered from team members.
2 Changes in Directory Structure
Rematch v2 made significant changes to its code and directory structure, using lerna for project management (as Rematch contains some plugins, it naturally fits the monorepo package management scheme of lerna). Let’s first look at the changes in the directory structure:
2.1 Rematch v1 Directory Structure
|
|
2.2 Rematch v2 Directory Structure
|
|
As you can see, the previous plugins are now managed as packages, and the core Rematch code is also a package called core. The two core plugins and pluginFactory were removed, with their code integrated into core, and new files like bag.ts
and dispatcher.ts
were added. I will discuss each one separately.
3 Removal of Rematch Class, Transition to Function
In my view, the most significant change in Rematch v2 is the removal of classes in favor of functions.
First, let’s take a look at the Rematch class in v1 (I will omit most of the code here, if you want to learn all of it, you can refer to the relevant articles in previous columns):
|
|
When called:
|
|
Actually, using “classes” here does not highlight any advantages. Now let’s take a look at v2:
|
|
In v2, a function is defined with a descriptive name createRematchStore
, and its usage is simpler:
|
|
The current Rematch maintainer, semoal, and I share the same viewpoint. He believes JavaScript is better suited for functional programming and prefers this approach. I previously wrote an article: Do you really understand Object-Oriented JavaScript? Is it necessary to use ‘classes’ for programming? In that article, I analyzed the differences between using pure objects and “classes” for programming. Often, we don’t need to use “classes.” In JavaScript, the essence of classes is still functions and objects. JavaScript class inheritance is fundamentally a prototypical chain. You can refer to that if you are interested.
4 Removal of Core Plugins and pluginFactory
Previous article mentioned Rematch’s plugin system, including pluginFactory and two core plugins, dispatch and effects. However, in v2, these were removed, and the relevant logic was integrated into core.
It is strongly recommended to read the previous column article before proceeding to this section.
4.1 pluginFactory
I believe the role of pluginFactory in Rematch v1 had two main purposes:
- To support
exposed
. Plugins could use theexposed
attribute to expose methods or objects, which would be bound to pluginFactory. - To bind the context
this
of all hooks to pluginFactory.
In Rematch v1, the two core plugins, dispatch and effects, each exposed dispatch
and effects
objects for other plugins to access in their hook functions. However, in v2, everything was transformed into functions to simplify the process. The effects
were moved to the rematch bag configuration that will be mentioned below, and dispatch
was directly operating redux.dispatch
.
4.1.1 Misuse of exposed
in v2.
Honestly speaking, I don’t think the early team did a particularly good job optimizing the pluginFactory part. For instance, binding all hooks’ this
to pluginFactory in the previous version facilitated convenient access to dispatch
and effects
. But in v2, dispatch
is directly equivalent to redux.dispatch
and needs to be accessed via rematchStore.dispatch
, while effects
were put into the rematch bag. When calling different hooks, the parameters passed are also inconsistent. However, I guess maybe other plugins didn’t frequently access dispatch
and effects
in their hooks, so no one paid attention to or pointed out these flaws.
Besides the above, another major issue in v2 is the misuse of the exposed
parameter. In v1, plugins could expose some methods or properties using the exposed
parameter, such as dispatch
and effects
, but this exposure was limited to between plugins (since it was mounted on the pluginFactory object). In v2, a specific addExposed
function was created to directly expose these methods or properties of the plugins to rematchStore
. Personally, I think this is a misunderstanding of the original design concept. Normally, achieving such a purpose should be implemented using the onStoreCreated
hook. If it’s just to add this feature, it also seems redundant.
Here is the code for addExposed
:
|
|
4.2 dispatch plugin
In v1, dispatch was mainly used to enhance redux.dispatch
, supporting chained dispatching of actions, such as dispatch.modelName.reducerName
. This logic was moved to the dispatcher.ts
file in v2.
4.3 effects plugin
The effects plugin did two things:
- Exposed global
effects
. - Used the
middleware
hook to handle side effects. - Supported chained dispatching of side-effect actions, such as
dispatch.modelName.effectName
.
In v2, the first point’s effects were moved to the rematch bag, the second point was handled by creating a specific function createEffectsMiddleware
, and the third point’s logic was also moved to the dispatcher.ts
file.
5 Introduction of Rematch Bag
In v2, an object called rematchBag
was added to store globally accessible information, such as the forEachPlugin
method from the previous v1 Rematch class, complete models, reduxConfig configuration, and global effects
exposed by the effects plugin.
|
|
6 New plugin hooks
In v1, plugins could use four hooks: onInit
, middleware
, onModel
, and onStoreCreated
. In v2, onInit
was removed (as it was only used in pluginFactory, which has been removed), and new hooks onReducer
and onRootReducer
were added, while middleware
was renamed to createMiddleware
.
6.1 middleware
was renamed to createMiddleware
Previously, the middleware
parameter directly accepted a Redux middleware. After being changed to createMiddleware
, it now accepts a function, which still returns Redux middleware, but it can pass the aforementioned rematch bag as a parameter. This change facilitates users in customizing their middleware.
In v1, the plugins using middleware were effects, typed-state, and subscriptions (which was removed in v2). In v2, only typed-state remains, and it has not yet utilized the rematch bag parameter.
For example, here is a comparison of usage between v1 and v2:
v1:
|
|
v2:
|
|
6.2 Removal of the onInit
Hook
The onInit
hook was called in the create
method of pluginFactory. I searched through the v1 code and did not find any plugin using this hook, so I am not clear about its specific function.
In v2, due to the removal of pluginFactory, the onInit
hook is also gone.
6.3 Addition of the onReducer
Hook
v2 added the onReducer
hook on top of onModel
, for more granular control over reducer behavior. This hook is called when generating an individual model reducer. If it returns a value, the final reducer is replaced by this return value; otherwise, the original reducer continues to be used.
As previously discussed with the immer plugin, v1’s immer was mainly implemented through the redux.combineReducers
configuration, which had an issue of being overridden. Therefore, in v2, the immer plugin was refactored with the onReducer
hook, and similarly, the persist plugin was refactored. This approach avoids the problem of combineReducers
being overwritten when using immer and persist simultaneously.
6.4 Addition of the onRootReducer
Hook
This hook is similar to onReducer
, but it is called when the final rootReducer
is generated. Currently, only the persist plugin uses this hook.
7 Summary
These are the main changes brought about by this upgrade. Although there are many changes, they have little actual impact on code execution, and the use of functional programming has made the code process clearer and easier to understand. However, there are still some issues, such as the upgrade team being different from the original Rematch v1 developers, and the original author no longer maintaining the framework, leading to some misunderstandings of the previous design concepts.
In addition, the v1 code used a lot of this
and did not follow a pure functional programming style, with various mutations in the code. In v2, the use of this
was reduced, but there were still quite a few mutations in the parameters. These all made the source code more complex and the process difficult to understand and follow.
In the next and final article, I will discuss my area of expertise, the Rematch type system. It is a complex system that I almost entirely rewrote in v2, greatly enhancing Rematch’s usability. However, there are still many remaining issues, some of which I may not be able to solve due to my limited knowledge, and others due to TypeScript’s design limitations. I will discuss these in the next article and hope that more skilled type gymnasts can join us to continuously improve Rematch and create the best experience. Stay tuned!