background

At present, the state management of major projects in the company is Redux. There are dozens of Reducers, but the number of reducers ranges from thousands to hundreds. The service states are many and miscellaneous, the process of transforming into view state is tedious, and most scenarios are difficult to reuse.

The business of HR HM on PC and mobile terminals repeats a lot of logic, and a requirement often needs to be developed several times.

Most components in the project directly connect to Redux to obtain data in the form of connect when needed.

trouble

In the early stages of using Redux, I always felt uncomfortable, but I couldn’t put my finger on what the problem was. After finishing, I found out what the problem was:

View state and business state are coupled

For the view component I only need a meetingRoomOptions to render the conference room items, but I’m forced to make some view-independent logical decisions within the view, first enabledMeetingRoomVendor to determine whether to use a three-party conference room or a system conference room. And then you sort it by locationId, which the view component doesn’t care about.

At the same time, if I develop the same requirements of the mobile terminal again, I may need to copy the logic of this state processing again, and at the same time, I will need to maintain more maintenance in the future, and the receiver will also be lost in a lot of unclear logical judgments.

There are a number of on-off states in the project that are used to determine particular business scenarios, and these states are stored in redux. If is to control the display of different components is not too big problem, but is often need to reuse the same components, there are some differences, lead to need to join a large number of judgment logic in the view code, used to handle business state into a view of the process, at the same time, need to be very careful in each place used for processing and any omissions will bring bug, After the final development of the components are often terrible, view-independent logic code everywhere, the code is not readable, easy to reuse and almost untestable, and because of the complexity of the logic, the development of unfamiliar code is likely to lead to some unexpected bugs.

Scope pollution

Not only is Eslint configurable, but the function component can’t be deconstructed as props because it needs to introduce the dispatching-related function (updateOrgMeetingRoom in this case). It’s also easy to get bugs if you deconstruct functions with the same name and change them later.

repetitive

Extremely verbose, adding a state requires multiple files to write and type support is poor, and reading a state change process requires reading multiple files.

High degree of coupling, call state is not constrained

High degree of coupling with project code, call redux state is not constrained, implicit calls and so on are difficult to find and clear.

invasive

Because connect is an invasive way to inject props to components, it can be expensive to change the state management framework at a later stage, like after I joked that Redux was hard to use

There is nothing we can do about the problems left over by history

unbridled

There are a thousand ways for people to get feedbackTemplateEntities. When you want to remove a field from a redux later, it’s a disaster.

performance

The design of mapStatetoProps does not filter out unwanted notifications. Every time a store changes, subscribed components are notified, resulting in multiple implementations of mapStatetoProps. A light state mapping has no impact on performance.

But almost everything the project needs right now is a direct connect to the Store, and the handling of business state is a lot repetitive, and the time calculations in mapStatetoProps are performed multiple times. MapStatetoProps is a new reference every time it is evaluated, causing the component to render multiple times.

But performance isn’t an issue right now, right? Maybe it’s because computers are so good these days.

explore

Clear requirements

  1. The worst smell is that the judgment logic and transformation logic are mixed with the view, which I need to decouple to facilitate reuse, testing, and maintenance.

  2. Scope pollution is prone to implicit bugs, and gymnastic state acquisition that pushes the limits of the human body is not tolerated. Calls need to be constrained and isolated.

  3. MapStatetoProps causes multiple computations, where time computations can be solved by using the resELECT library, but the best practice is to call on demand, I only want the status updates I’m interested in, while time computations I want to cache.

  4. The development of verbose is the reality, the ideal utopia is the hope that you can foolishly increase the state.

Why wasn’t I aware of state management in my previous company’s development?

You can’t feel the difference without comparing.

My previous company used Angular for development, and all state was managed based on services

Interfaces are used to connect services and components. Angular uses DI to decouple view components from services. This makes it easy to reuse and maintain services.

  • Views and view services (or concrete services) are connected through interfaces

  • Concrete services are connected to abstract services through interfaces

  • The view is only responsible for the interaction logic, and the state is managed by the specific service. It doesn’t care how it comes from, only what I want (interface).

So much so that I gave up the third-party state management libraries to build the framework in favor of Services and Angular’s powerful DI

How should states flow

My ideal front-end state flow:

Views are responsible only to the user for rendering view state and responding to user interactions

Each view component has its own representational service, which is responsible only for the view interface

The structure between services is from abstraction to representation, which in principle prohibits high-level services from invoking low-level services

The abstract service does IO related side effects, smoothing differences and other content

Solve problems based on Redux

If it would be impractical and costly to completely decouple the project from redux, the ideal approach would be to decouple the redux from the view by removing the link between redux and the view bit by bit.

  • Based on the above thinking, I decided to insert the service layer between Redux and view, discard Connect, and continue to use Redux to manage service state. The reducer service and view service are decoupled to improve reuse.

  • In order to decouple services from Redux, the subscription logic provided by Redux is deprecated and redux middleware and RXJS are used to implement publish subscription. (It’s ok to implement a published_subscribe myself, but I want to abstract events, state changes, etc. into streams through RXJS)

  • Decouple services in DI mode

The middleware logic is as follows:

To inject a responsive Store into a project via DI:

Build the service:

Consumption of the service:

benefit

  • No longer being held hostage by the state management repository, decoupled completely, and in the future you can get rid of Redux if you want to

  • Forcing you to think about view state and business state solves the transition from business state to view state and improves code quality

  • Status call display and subject to constraints, I give you you can take, I do not give you can not rob

  • By smoothing out the differences between different situations, business logic can be abstrused for use across multiple platforms, while testing code is much easier to write

  • In the React Redux context, avoid performance problems caused by mapStatetoProps and reduce render times and time calculations. Views are notified only after the state changes they subscribe to. Custom compare can cache time calculations in a different way

The cost of

  • Development costs go up, business state and view state are forced to think, services need to be written, but the long-term benefits are greater

  • The service that is being reused needs to be tested

  • For the time being, only wedi, the DI library, can meet the requirements. It may be necessary to find another relatively stable DI library for non-personal maintenance, or the team can maintain it by itself

  • Services need to be quantified to be powerful, and old components need to be refactored

Subsequent consider

  • Now that the service services have been removed, consider multiplexing the services on different ends

other

After a chat with TL today, I learned that the project has some functions that abstract the transition state in a file, but few people use them, and this abstract way, it is difficult to synchronize the methods to anyone except the author. And it just reuse some of the common logic, which is uncomfortable.

In addition, difficulties are not the fault of Redux. Redux is very useful in small and medium projects. In large projects, Redux does not deal with these difficult cases, which is caused by the abuse of users.

[Beijing or Chengdu] [20k-30k] [React] [Typescript] [Typescript] Moka recruit front end friends. If you want to send your resume to me at 📮, tweet it to [email protected]

Reference:

  • Github.com/wendellhu95… The DI implementation in React references this article