This series is an excerpt from the React Conf 2021 sharing session, which aims to learn more about the details of the event besides the core content.

Notes will carry personal understanding and may use a lot of their own summed up words, may produce partial understanding deviation, if there is a wrong understanding welcome correction! It is recommended that you still have time to watch the video content to learn about native sharing content.

React without memo

Shareer: Xuan Huang

Main Contents:

  • React and Memo
  • React without memo
  • React Forget

React without Memo

React Conf 2021 React Conf 2021 Replay

React and Memo(React and Memorization)

Let’s take a look at an example of a TODO application:

The code is shown on the left, and the running result is shown on the right.

When we open the console and use the React Devtool to debug the above code, it is not difficult to find that frequent complete operations and add operations will increase the number of existing list items update. As you can see in the image below, the oops button (for the TODO app, select to complete the item and click again to cancel it) is rerendered for the rest of the memo, useCallback, and useMemo. The list items may contain dozens or hundreds of pieces of data (not considering the scenario of virtual lists), and the updates carried by them are heavy points that may cause performance problems.

To fix this, a common approach is to use react. memo with useCallback hooks to modify the code.

If you don’t know React. Memo, it is recommended that you read the official document: React. Memo

The transformation results are shown in the figure below:

First, we wrap the component that originally rendered a single to-do item with react. Memo to perform a function similar to pureComponents. The component will not be rerendered if the last two shallow comparisons are the same. At the same time, in modules that use
components, handleChange is wrapped useCallback to prevent function components from recreating handleChange during the update process with unchanged dependencies. The value stored in the previous handleChange changes.

Through this transformation, we can clearly perceive from the debugging feedback on the right, and continue to fix! The remaining to-do items are not updated when the operation is performed.

However, TODO applications may be more complex than we think. TODO applications may still require some additional features, such as filters; For a market-oriented TODO application, however, some functionality such as themes is still required.

So we need to keep expanding our code.

First of all, we added filter function, through the selection of classified treatment of work items for screening;

Second, we added the theme function, by passing a theme color to simply indicate the scene;

Next, continue through the page action to see the specific feedback:

While we were selecting the theme color, the page was getting slow feedback.

The reason is that when we make theme color changes, the
and
components will be re-rendered, and the getFiltered() method will be recalculated as the
component is re-rendered. Changing the color value, from our subjective understanding, does not affect the result that can show the to-do list (only changes the theme color and other results such as style rendering).

To fix this, we need to use the hooks of useMemo to cache the results.

const filtered = useMemo(
  () = > getFiltered(todos, visibility),
  [todos, visibility]
);
Copy the code

When todos and visibility change, you need to re-call the function to get the results and cache them, and when they do not change, you apply the cached values directly to the filtered variable. Thus, you can think of useMemo optimization as a computational property in VUE, an optimization for some of the high computational costs.

Through the above transformation, we have a relatively smooth user experience. But looking at the full code, the developer experience doesn’t seem particularly satisfying. Specific thinking is as follows:

  • We had to write a lot of extra code (e.g., declaring dependencies, wrapping components for shallow comparison, using hooks for performance) to satisfy our vision of a smooth experience;

  • We also need to clarify the dependencies between the data in the code to prevent this cache optimization from being broken during subsequent iterations.

React Without Memo (You can achieve a similar user experience without using react. memo and other optimizations)

The community has been thinking about how to make React easier to use, and the React team was caught in a dilemma by the example in the previous section. If the developer experience is dominant, the user experience will suffer; By focusing on the user experience, developers will have to put up with increased complexity and difficulty in getting started.

The React team put together a mix of user experience, developer experience, JSX template syntax, hooks fixes, and the reality of class, which was originally immutable rendering and easier to track, and decided there was something missing.

Here, we’ll show some of the React team’s research in this direction and how it addresses this problem.

How to memorization? How to memorize/cache

After stripping out optimizations, hooks, and so on from the TodoList demo, the bottom line is that the
function component takes props and state as input values, outputs a UI rendering and side effects, and needs to create intermediate variables, For example filtered and handleChange do something in the process.

The original: The TodoList is just a function that takes inputs from props and states and generate outputs such as UI and effects. And the need to create intermediate value, such as filtered and handleChange to do it.

The figure above shows state (todos), props (visibility, themeColor) as inputs to the
component, while creating filtered and handleChange variables in the component. Each applies to a different input value (or none), and the output of this component is a UI (and the side effects that this UI contains) that can either apply the input value directly or the intermediate value reprocessed from the input value.

Ideally, we just want the
component to re-execute the getFiltered() method when visibility or todos changes. Is there a way todo this without using useMemo?

Imagine if we could have some magic variable that tells us whether its input has changed?

For example, can we implement our logic through the above pseudocode? But there’s another question, what is this variable on the else branch?

Here we need to think about what does useMemo actually do?

UseMemo essentially returns the value of the last render result cache, so if we directly copy a code segment with the same logic as useMemo, and calculate and store the result in the cache and be able to read, can we achieve the same purpose? The relevant code is as follows:

let filtered;
if (hasVisibilityChanged || hasTodosChanged) {
  filtered = memoCache[1] = getFiltered(todos, visibility);
} else {
  filtered = memoCache[1];
}
Copy the code

Similarly, handleChange, which is optimized using useCallback, can be rewritten as follows:

UseCallback and useMemo useCallback(FN, DEP); UseMemo (() => fn, dep);

const handleChange = memoCache[0] || (
  memoCache[0] = todo= > setTodos(todos= > getUpdated(todos))
)
Copy the code

What else can we do?

We haven’t dealt with themeColor, which is passed directly from input to output, but can also be written this way:

const jsx_addTodo = hasThemeColorChanged ? (
  memoCache[3] = <AddTodo setTods={setTods} themeColor={themeColor}>
) : memoCache[3]
Copy the code

For the expensive overhead of list rendering, it is also possible to cache it, but it seems to share the same lifecycle as filtered, so this section can be elevated, for example:

let filtered, jsx_todos;
if (hasVisibilityChanged || hasTodosChanged) {
  filtered = memoCache[1] = getFiltered(todos, visibility);
  jsx_todos = memoCache[2] = (
    <ul>
      {
        filtered.map(todo => (
          <Todo key={todo.id} todo={todo} onChange={handleChange} />))}</ul>)}else {
  filtered = memoCache[1];
  jsx_todos = memoCache[2];
}
Copy the code

We ended up extracting this part into a large if statement so that we could apply the same policy to all UI parts of the component, regardless of which part was changed.

The React team explored automatic caching and made React easier to use.

  • You can achieve similar results without writing useMemo, useCallback, and React.memo in your project code
  • We’ll help you (through compiler conversion code) create variables like the ones described above, build large if judgments and apply caching automatically. This is the way to achieve this seemingly magical effect, but the resulting code will not be exactly the same.

React Forget (React Forget, a compiler that automatically caches memory)

React Forget is a compiler that enables components to automatically cache memory. The compiler is currently under development and has not been officially released in production. The team is working on some of the harder questions, and at worst, the research is bound to fail.

React Forget will currently prove its feasibility in the Meta (or better known as Facebook) business scenario scale, and will open source and release alpha versions of the compiler after approval.

To sum up:

  • React Forget is a compiler
  • The core function of React Without Memo is to recompile components that have not been cache-optimized
  • React Forget is likely to be a much more complex scenario to handle, so there will be a lot of difficulties for the team to solve, and it will only be released open source and on the agenda once the app is implemented on the scale of the Meta internal project.
  • The worst-case scenario is that the Study of React Forget will fail.