# 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.

<!--more-->

From this article onwards, we will delve into Rematch v2.

## 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](https://github.com/rematch/rematch/issues/787) 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](https://github.com/rematch/rematch/releases?after=%40rematch%2Fimmer%402.0.0-next.1).

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.

## Changes in Directory Structure

Rematch v2 made significant changes to its code and directory structure, using [lerna](https://github.com/lerna/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:

### Rematch v1 Directory Structure

```
...
plugins
|—— ...
|—— loading
|—— immer
|—— select
src
|—— plugins
|  |—— dispatch.ts
|  |—— effects.ts
|—— typings
|  |—— index.ts
|—— utils
|  |—— deprecate.ts
|  |—— isListener.ts
|  |—— mergeConfig.ts
|  |—— validate.ts
|—— index.ts
|—— pluginFactory.ts
|—— redux.ts
|—— rematch.ts
```

### Rematch v2 Directory Structure

```
...
blog
docs
website
packages
|—— core
|  |—— test
|  |—— src
|  |  |—— bag.ts
|  |  |—— config.ts
|  |  |—— dispatcher.ts
|  |  |—— reduxStore.ts
|  |  |—— rematchStore.ts
|  |  |—— types.ts
|  |  |—— validate.ts
|  |  |—— index.ts
|  |—— node_modules
|  |—— ...
|—— immer
|  |—— test
|  |—— src
|  |  |—— index.ts
|  |—— node_modules
|  |—— ...
|—— loading
|—— select
|—— ...
```

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.

## 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):

```ts
/**
 * Rematch class
 *
 * an instance of Rematch generated by "init"
 */
export default class Rematch {
  protected config: R.Config;
  // ... other private configs

  constructor(config: R.Config) {}

  public init() {
    // ...

    const rematchStore = {
      name: this.config.name,
      ...redux.store,
      // ...
    };

    return rematchStore;
  }
}
```

When called:

```ts
export const init = (initConfig: R.InitConfig = {}): R.RematchStore => {
  const name = initConfig.name || count.toString();
  count += 1;
  const config: R.Config = mergeConfig({ ...initConfig, name });
  return new Rematch(config).init();
};
```

Actually, using "classes" here does not highlight any advantages. Now let's take a look at v2:

```ts
export default function createRematchStore<
  TModels extends Models<TModels>,
  TExtraModels extends Models<TModels>
>(config: Config<TModels, TExtraModels>): RematchStore<TModels, TExtraModels> {
  // ...

  const reduxStore = createReduxStore(bag);

  let rematchStore = {
    ...reduxStore,
    name: config.name,
    // ...
  } as RematchStore<TModels, TExtraModels>;

  return rematchStore;
}
```

In v2, a function is defined with a descriptive name `createRematchStore`, and its usage is simpler:

```ts
/**
 * Prepares a complete configuration and creates a Rematch store.
 */
export const init = <
  TModels extends Models<TModels>,
  TExtraModels extends Models<TModels> = Record<string, never>
>(
  initConfig?: InitConfig<TModels, TExtraModels>
): RematchStore<TModels, TExtraModels> => {
  const config = createConfig(initConfig || {});
  return createRematchStore(config);
};
```

The current Rematch maintainer, [semoal](https://github.com/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?*](https://juejin.cn/post/6844904137688121352) 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.

## Removal of Core Plugins and pluginFactory

[Previous article](https://tianzhich.com/en/rematch-series-plugin-factory-and-core-plugins/) 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](https://tianzhich.com/en/rematch-series-plugin-factory-and-core-plugins/) before proceeding to this section.

### pluginFactory

I believe the role of [pluginFactory](https://tianzhich.com/en/rematch-series-plugin-factory-and-core-plugins/#plugin-factory) in Rematch v1 had two main purposes:

1. To support `exposed`. Plugins could use the `exposed` attribute to expose methods or objects, which would be bound to pluginFactory.
2. 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`.

#### 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`:

```ts
/**
 * Adds properties exposed by plugins into the Rematch instance. If a exposed
 * property is a function, it passes rematch as the first argument.
 *
 * If you're implementing a plugin in TypeScript, extend Rematch namespace by
 * adding the properties that you exposed from your plugin.
 */
function addExposed<
  TModels extends Models<TModels>,
  TExtraModels extends Models<TModels>
>(
  store: RematchStore<TModels, TExtraModels>,
  plugins: Plugin<TModels, TExtraModels>[]
): void {
  plugins.forEach((plugin) => {
    if (!plugin.exposed) return;
    const pluginKeys = Object.keys(plugin.exposed);
    pluginKeys.forEach((key) => {
      if (!plugin.exposed) return;
      const exposedItem = plugin.exposed[key] as
        | ExposedFunction<TModels, TExtraModels>
        | ObjectNotAFunction;
      const isExposedFunction = typeof exposedItem === "function";

      store[key] = isExposedFunction
        ? (...params: any[]): any =>
            (exposedItem as ExposedFunction<TModels, TExtraModels>)(
              store,
              ...params
            )
        : Object.create(plugin.exposed[key]);
    });
  });
}
```

### 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`](https://github.com/rematch/rematch/blob/main/packages/core/src/dispatcher.ts) file in v2.

### effects plugin

The effects plugin did two things:

1. Exposed global `effects`.
2. Used the `middleware` hook to handle side effects.
3. 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`](https://github.com/rematch/rematch/blob/main/packages/core/src/dispatcher.ts) file.

## **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.

```ts
/**
 * Object for storing information needed for the Rematch store to run.
 * Purposefully hidden from the end user.
 */
export interface RematchBag<
  TModels extends Models<TModels>,
  TExtraModels extends Models<TModels>
> {
  models: NamedModel<TModels>[];
  reduxConfig: ConfigRedux;
  forEachPlugin: <Hook extends keyof PluginHooks<TModels, TExtraModels>>(
    method: Hook,
    fn: (content: NonNullable<PluginHooks<TModels, TExtraModels>[Hook]>) => void
  ) => void;
  effects: ModelEffects<TModels>;
}
```

## **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`.

### `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：**

```ts
const typedStatePlugin = (): Plugin => ({
  // ...
  middleware: (store) => (next) => (action) => {
    // ...
  },
});
```

**v2：**

```ts
const typedStatePlugin = <
  TModels extends Models<TModels>,
  TExtraModels extends Models<TModels> = Record<string, any>
>(
  config = DEFAULT_SETTINGS
): Plugin<TModels, TExtraModels> => {
  // ...

  return {
    // ...
    createMiddleware: () => (store) => (next) => (action) => {
      // ...
    },
  };
};
```

### 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.

### 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](https://tianzhich.com/en/rematch-series-third-party-plugins/#immer), 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.

### 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.

## 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!

