This article is translated from Redesigning Redux by Shawn McKay, author of Rematch

TS fanatics, promoted Rematch V2 update (mainly improved type system compatibility), currently Rematch version 2 still has some issues, documentation needs to be improved, interested students welcome PR. I plan to write a column dissecting Rematch’s design, and this translation is the first

The following is the transcript:


Isn’t how to do state management a solved problem by now? Intuitively, developers seem to know that state management is harder than it should be. In this article, we’ll explore the following questions that you’ve probably asked yourself:

  • Do you need a third-party library for state management?
  • Does Redux deserve this popularity? Why is it worth it? (or why not?)
  • Can we develop a better state management solution? If so, how?

Does state management require additional use of third-party libraries?

Being a front end engineer involves more than just dealing with pixels on a screen. The real art of development is knowing how to store and manage data. In short: it’s complicated, but not that complicated.

Let’s look at what we do when using a component-based view framework such as React:

1. Component State

The state that exists in the component itself. In React, imagine updating the state with setState.

2. Relative State

State passed from parent to child. In React, imagine the props passed to the child component.

3. Provided State

The state provided by the root Provider is accessible to any Consumer node in the component tree, except for adjacent component nodes. In React, imagine the Context API.

Most of the state is in the view layer and determines the UI presentation. But what if partial state code affects the underlying data and logic?

Putting all state data in the view layer can violate the principle of separation of concerns: the data is then coupled to some of JavaScript’s visual libraries, and the code becomes harder to debug, even more annoying: you have to constantly think and adjust where to put your data.

As the design changes, state management becomes more complex, and it can be difficult to determine which components need which state data. The most straightforward approach is to put all the data in the root component, and if so, it is better to use the following solution.

4. External State

Status data can be moved outside of the view library. This allows the view layer to keep up with the data using the provider/consumer pattern “Connect.”

Perhaps the most famous state library for doing state management in this way is Redux, which has gained tremendous popularity over the past two years. Why does a simple library get so much traction?

Does Redux have better performance? No, it will also be slightly slower when you process an action.

Is Redux easy to use? Of course not.

The simplest, of course, is Pure JavaScript:

So why don’t people use global.state = {}?


Why Redux?

The bottom layer of Redux is actually the global empty object mentioned above, but it contains some operation flows:

In Redux, you can’t modify state data directly. There is only one method for modification: dispatch an action to the above pipeline, and finally update the data.

Pipelining contains two types of monitoring: Middleware and subscriptions. Middleware is functions that listen for action distribution and then use tools such as Logger, DevTools, Or call a listener function called “syncWithServer”. Subscriptions are also functions that broadcast status change events.

Finally, the reducers function for updating breaks the overall state change into smaller, more modular, and more manageable chunks. Reducers update functions that can break down state changes into smaller, more modular and manageable chunks.

On the development front, using Redux is probably simpler than using global objects for state management.

You can think of Redux as a library consisting of a global object, plus a reducer method to update that object, and some hooks before and after updating that object.


But isn’t Redux too complicated?

Redux is complicated. There are several undeniable signs that can be used to measure whether an API is worth improving, summed up in the following equation:


t i m e _ s a v e d t i m e _ i n v e s t e d = q u a l i t y _ o f _ a p i \frac{time\_saved}{time\_invested} = quality\_of\_api

Time_saved indicates the development time saved by using the API, while TIME_Invested indicates the time spent in using the API, including reading documents, studying tutorials, and searching for unfamiliar concepts.

Redux is essentially a simple, lightweight library, but it can be complicated to learn. For developers who are familiar with functional programming, they have overcome the complex learning process and benefit from Redux. But there may be some developers who get lost in learning and think, “This isn’t for me, I’ll just use jQuery.”

You don’t need to know “comonad” to use jQuery, and you don’t really need to understand functional composition for state management.

Any library should be a abstraction that encapsulates complex logic and makes it easy to use.

I don’t mean to provoke Dan Abramov. I just think Redux became popular too early, and it’s not mature at the moment.

  • If a library is used by many developers, how do you refactor it?
  • If you publish a disruptive changebreaking change) would affect countless projects around the world. How do you justify doing that?

You can’t do either. But you can provide rich documentation, easy-to-understand videos, and an active community to better support existing projects. Dan did a really good job of that.

Or better yet.


Redesign Redux

I think Redux should be rewritten. I will explain it from the following seven aspects:

1. Setup

Let’s look at the following figure, where on the left is the basic configuration of a Redux project:

Many developers are probably stuck here, just initializing for the first time, as if staring blankly into an abyss. What is thunk? What is compose? How else can we use this function?

Imagine if Redux replaced function composition with configuration, the initialization process might look something like the one on the right.

2. Reducers

Reducer in Redux can be reduced in a different way from the tedious writing we are used to (using switch statements).

Assuming that a Reducer case branch matches action.type, we can reverse the parameters so that each Reducer is a pure function (receiving state and action). It’s even easier to standardize the action and keep only the state and payload parameters.

Translator’s note: The third part has also been omitted from the original text

4. Use Async/Await instead of Thunks

Thunks are widely used to create asynchronous actions in Redux. Out of all the asynchronous solutions, Thunk’s solution is more of a smart hack than an official recommendation. The Thunk workflow is as follows:

  1. You send out an action that is a function rather than an expected object.
  2. The Thunk middleware checks whether each action is a function.
  3. If it is a function, the middleware calls the function and passes in two methods that can access the store:dispatchgetState.

Are you serious? Isn’t it a bad habit to treat a simple action dynamically as an object, a function, or even a Promise?

Looking at the above example on the right, can’t we just use async/await?

5. Two types of Action

When you think about it, there are essentially two kinds of actions:

  1. Reducer Action: The Reducer is triggered and the state is updated
  2. Effect Action: trigger asynchronous action. A Reducer action may be sent in this action, but no state is directly updated in the asynchronous function

It’s helpful to distinguish between the two actions and avoid confusion when using Thunk.

6. Redundant action types are no longer required

Why should Action Creators and Reducers be considered differently? Can we just have one? Can you change one without affecting the other?

Action Creators and Reducers are two sides of the same coin

The definition of const ACTION_ONE = ‘ACTION_ONE’ is redundant because action Creators and reducers are treated differently. By treating them as one thing, you don’t have to create a lot of files to export these string constants of action type.

7. Reducers 就是 Action Creators

Categorize reducer according to different uses and you can have the following simpler code:

The corresponding Action Creator can now be automatically determined from the Reducer, and in this scenario the Reducer can even become the Action Creator.

Following the basic naming conventions, the following conclusions can be drawn:

  1. If a Reducer name is “INCREMENT”, then the action type is “increment”. Or better yet, add another namespace: count/increment.
  2. Each action transmits data via “payload”.

Now, from a Reducer like count. Increment, we can generate an Action Creator.


Good news: We can have a better redux solution

We built Rematch because of the problems mentioned above.

Rematch encapsulates Redux and provides a simpler API without reducing Redux configuration capabilities.

Take a look at a complete Rematch usage example:

I’ve been using Rematch in production for the past few months, and as a recommendation, I would say:

I’ve never spent so little time thinking about how to do state management.

Redux is not going away, and nor should it. Let’s embrace the simple design patterns behind Redux with a flatter learning curve, fewer template configurations, and less mental drain.

Try Rematch and see if you like it.

If you like it, please give us a like on GitHub to let more people know.