SoFunction
Updated on 2025-03-10

Detailed explanation of best practice examples of react backend system

1. Selection of technical stacks for middle and backend systems

This article mainly talks about three parts: the selection of the technology stack of the middle and backend systems, the selection of the state management library in the hooks era, and the use of hooks problems and solutions.

1. What to do

Our goal is to build a front-end project best practice for the company's internal middle and back-end systems.

2. Requirements

Due to the high business needs, a developer needs to be responsible for several backend systems. Therefore, project best practice requirements are ranked by importance:

1. Development efficiency.

2. Maintainability.

3. Performance.

In short, high efficiency and simple code structure of development are two more important points.

3. How to choose a technology stack

Since our front-end technology stack mainly uses React, the basic framework uses React and React-router. The project development content mainly focuses on the background system page, so antd is selected as the system's UI framework. Then ahooks provides the useAntdTable method to help us save the workload of secondary encapsulation, so ahooks is used as the hooks library that the project mainly uses. Finally, considering development efficiency and performance, the state management library uses MobX.

The following will explain in detail the selection process of the status management library.

2. Selection of state management library in the hooks era

If hooks libraries with data request solutions such as ahooks or React-Query are used, they have already shared a large part of the work of the state management library. The rest is the problem of the interaction state processing of the page, mainly solving the problem of cross-component communication.

Currently, there are the following status management plans for the survey:

context

The first thing to consider is to not introduce any state management library and directly use the context method provided by the React framework.

React context is very convenient to use on the surface. Just define a provider and pass in data. When using it, use useContext to get the corresponding value.

However, this solution requires developers to consider how to deal with the problem of repeated rendering of components, and developers need to consider whether to manually split the data of the provider or use memo and useMemo to cache components (see detailshere)。

In general, it is still quite troublesome to solve. Every time you add the status, you need to check whether this value needs to be split, whether it is updated frequently, and how to organize components more reasonably.

Summary: React context development efficiency is not high and it is troublesome in post-maintenance.

// Requires split state< value={userData}>
  < value={menuData}>
    {}
  </>
</>
// Requires cached componentsuseMemo(() => <Component value={a} />, [a])

redux

Next is the most downloaded redux in the React state management library.

The first thing to complain about is the cumbersome writing method of the redux scheme. Every time you use it, you have to worry about how to name the action; when using reducer, you have to write a lot of extension operators, and a request must write at least three states (sending a request, successful request, and failed request); using thunk asynchronously will be despised that it is not elegant enough, and the saga API has many generators and is not easy to use.

The official Redux Toolkit framework solves the naming problem of the action mentioned above, and also the problem of reducing the problem of writing a bunch of extension operators. However, the problem of too fine state granularity still exists, and the way of writing saga remains unchanged.

If you combine ahooks, you can save saga, but after using these request libraries, the advantages of status tracking advocated by redux will disappear most of the time (although I feel that this function is useless). It feels too heavy to simply solve cross-component communication, and

And compared with other state libraries, Redux Toolkit, is still not easy to use.

Summary: Redux is really cumbersome and cumbersome.

// The code source is [Case] ​​on the Internet (/s/react-ts-redux-toolkit-saga-knq31?file=/src/api/user/:1752-1761)// The functions implemented by this piece of code can be done with just a few lines.export const createSagaAction = <
  PendingPayload = void,
  FulfilledPayload = unknown,
  RejectedPayload = Error
>(typePrefix: string): SagaAction<PendingPayload, FulfilledPayload, RejectedPayload> => {
  return {
    request: createAction<PendingPayload>(`${typePrefix}/request`),
    fulfilled: createAction<FulfilledPayload>(`${typePrefix}/fulfilled`),
    rejected: createAction<RejectedPayload>(`${typePrefix}/rejected`),
  }
}
export const fetchUserAction = createSagaAction<
 User['id'],
 User
>('user/fetchUser');
export function* fetchUser(action: PayloadAction<User['id']>) {
  try {
    const user = yield call(getUser, );
    yield put((user));
  } catch (e) {
    yield put((e));
  }
}
export function* userSaga() {
  yield takeEvery(, fetchUser);
}
export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {},
  extraReducers: (builder) => (
    builder
      .addCase(, (state) => {
         = true;
      })
      .addCase(, (state, action) => {
         = false;
         = ;
      })
      .addCase(, (state, action) => {
         = false;
         = ;
      })
  )
});

recoil

The official also recommended a status management library called recoil, which doesn't feel easy enough to use.

There are two commonly used APIs for defining states: atom and selector. Atom is written with key every time it is used, and the selector feels a bit redundant. The called API is also divided into useRecoilState and useRecoilValue. In terms of simplicity, it is completely destroyed by the zustand mentioned below.

Then the framework itself is relatively new, and the number of npm downloads is much lower than that of zustand.

Summary: The simplicity was completely sensible by Zustand, and the download volume was not high.

// Define atom and selectorconst a = atom({
  key: "a",
  default: []
});
const b = selector({
  key: "b",
  get: ({ get }) => {
    const list = get(a);
    return () > 0.5 ? (0,  / 2) : list;
  }
});
// Calling distinguishes between useRecoilValue and useRecoilStateconst list = useRecoilValue(b);
const [value, setValue] = useRecoilState(a);

zustand

Then when it comes to Zustand, the download volume of npm can already catch up with MobX.Trend comparisonThe basic calls are really concise. Define the store through create and call it directly when using it. It's much more convenient to use than recoil.

However, the state of zustand is immutable. When gettingState, you need to use many extension operators like redux. The official recommendation is to introduce immer, but this way of writing has become a little more complicated.

In addition, the granularity needs to be quite fine when defining the store, otherwise the problem of repeated rendering of components will be difficult to solve. Unlike MobX, you can write the store of the same page into a file. The dimensions split by zustand need to be divided according to the rendering status of the component.

If you can do this like React toolkit, it would be quite good if you don't need users to introduce immer by yourself. Because generally speaking, the interactive class status does not have many states, and the problem of too fine splitting of particles is not big. It's quite annoying to have developers manually add immers every time.
Summary: Additional libraries need to be introduced, and store splitting requirements are relatively detailed.

// Define storeimport produce from 'immer';
// If the value of list is not removed, modifying the value of title in component A will cause the rendering of component B where list is located.const useStore = create<TitleState>((set) => ({
  title: '',
  list: [],
  setTitle: (title) => set(produce((state: TitleState) => {
     = title;
  })),
}));
// Component A uses titleconst { title, setTitle } = useStore();
// Component B uses listconst { list } = useStore();

MobX

Yes, after talking about the status management library, I finally chose MobX.

MobX is very simple to use, mainly using two APIs: useLocalStore and useObserver. The store can be divided according to the page, which is very convenient to maintain. The performance is good, just split the components according to the store value.

As for the statement that React and MobX are not as good as vue, it may be in terms of performance. But essentially speaking, choosing React mainly focuses on the powerful ecological environment derived from React, rather than other reasons.

A typical example is React Native. If there is a need for cross-end development of APPs, then React Native is still a popular solution. At present, React has a height that other frameworks cannot reach in terms of ecological maturity. Front-end teams can use React as a framework to solve the development needs of multiple scenarios such as web, app, and server rendering. Using a technology stack can reduce development costs. It is relatively low for developers to switch development scenarios and does not need to learn additional framework syntax.

So there is no need to compare with other frameworks. Since you have chosen React, just find a useful state management library in the React system and don’t have any other psychological burdens.

// A file defines a page storeclass ListStore {
    constructor() {
        makeAutoObservable(this);
    }
    title = '';
    list = [];
    setList(values) {
         = values;
    }
}
// useconst localStore = useLocalStore(() => store);
useObserver(() => (
    <div>
      {((item) => (<div key={}>{}</div>))}
    </div>
));

Supplement: Regarding the issue of repeated rendering of React components, some comments on the Internet think that it doesn't matter.

But if you don’t care, you may encounter performance problems when the project becomes complicated over time, and it will become more difficult to modify it.

Even if you spend a lot of effort to reconstruct, you will face testing problems. You need to apply for testing resources to regress the business functions after the project is launched, which is still quite troublesome.

MobX is very convenient to handle the problem of repeated rendering of components. As long as the components are split properly, developers do not need to pay too much attention.

3. Problems and solutions for hooks

After selecting the technology stack, the next step is to determine the development form of React code.
First of all, it is necessary to use hooks in React projects at present, which is the official route.

questionHowever, using hooks will encounter two more troublesome problems. One is the problem when there are too many dependencies for APIs such as useEffect, useCallback, and useMemo. Another is the use of useEffect.

Dependency issues:

Let’s talk about the dependency problem first. The most troublesome thing when encountering APIs such as useEffect, useCallback, and useMemo in the project is that several dependencies are followed. When you want to modify the functions in it, you must check the specific functions of each dependency and understand their update period. The newly added state needs to be considered whether to use useState or useRef, or whether the two coexist. In short, the mental burden is quite high.

// Need to view the update logic of each dependencyconst onChange = useCallback(() => {
    if (a) {
      setValue(b);
    }
}, [ a, b ]);

useEffect problem:

Next is the use of useEffect. Whether you see a useEffect with multiple dependencies or multiple useEffect with different dependencies in the project, it is a headache.

When you need to add or modify the code logic inside, you need to understand the code once and then decide whether your new code logic is written in the existing useEffect or add a new useEffect to undertake it.

// There are multiple dependencies in a useEffectuseEffect(() => {}, [a, b, c])
// Multiple useEffects follow their respective dependenciesuseEffect(() => {}, [a])
useEffect(() => {}, [b])
useEffect(() => {}, [c])

SolutionThe previous decision was made to be a state management library, so the solution to these two problems is to try not to use useState, the server interface requests are used to solve the problem using ahooks, and the remaining interactive state is handled by mobx.

Multiple dependencies problem:
First, let’s look at the solution to too many dependencies. After using the state of mobx, the dependency only needs to write a store one dependency (it’s fine if you don’t write it). At this time, the latest values ​​in the store are obtained in the APIs such as useEffect and useCallback, so you don’t need to worry about state update issues.

// Just write a localStore dependency, and the a and b values ​​in it are always the latestconst onChange = useCallback(() => {
    if () {
      ();
    }
}, [ localStore ]);// Can also use []

UseEffect problem:

Then there is the problem of using useEffect, and the solution is not to use useEffect.

Like the above solution with many dependencies, the server interface requests are all solved using ahooks, and the component rendering state uses mobx and other hooks methods provided by ahooks (ahooks documentation), basically you can't use useEffect.

If there is a need to listen for a certain value and render components with deep nesting levels, such as the scene where the parent component needs to perform the clearing action after a certain state of the parent component is changed, then you can use MobX's reaction to handle it at this time.

// Triggered after the status changesreaction(
    () => ,
    visible => {
      if (visible) {
        ?.resetFields(); // Clear the form      }
    }
);

Supplement: The problem of MobX component reuse can be solved by referring to the writing method provided by the official document and passing in a function that returns different state values.

// Official recommendationconst GenericNameDisplayer = observer(({ getName }) => <DisplayName name={getName()} />)
const MyComponent = ({ person, car }) => (
    <>
        <GenericNameDisplayer getName={() => } />
        <GenericNameDisplayer getName={() => } />
        <GenericNameDisplayer getName={() => } />
    </>
)

Summarize

1. The system's technology stack is React, React-router, antd, ahooks and MobX.

2. Selecting MobX in the state management library can take into account development efficiency, post-maintenance and performance issues.

3. The solution to hooks problem is to use ahooks to process the server state, and then use MobX to process the remaining interactive state; try to use useState as little as possible and not use useEffect.

4. A code template will be added in the future to supplement the specific code organization forms of some commonly used backend system pages.

5. The significance of best practice lies in unifying the code writing within the team to achieve the goal of reducing project development costs and collaborative costs among colleagues. Because the consistency of the code structure can facilitate the later maintenance of the project. Suppose React officially launched a new code organization form, then a project with a unified structure can be quickly moved to the new writing method (the ideal situation is to write a script to replace the code in batches). Moreover, the team's developers can quickly understand the structure and functions of different projects, and there will be no situation where only a colleague can develop a project.

6. The best practice in this article is designed based on the situation of your own team. If you also value development efficiency and later maintenance, you can refer to this model.

The above is the detailed explanation of the best practice example of react backend system. For more information about react backend system practice, please pay attention to my other related articles!