Pre-knowledge:

  1. This article is not intended for newcomers to the react/vue foundation
  2. Remember this article: Relearn React — Render those things. Some contexts are linked

Let’s start with a piece of code

Start with a simple demo code:

  • The parent componentFatherThere is acountstate, has two child componentsSonAnd adiv
  • First child componentSonAccepts from the parent componentprops
  • Second subcomponentSonIt’s just a pure text display that doesn’t involve any states
  • Click on the parent componentdiv.countWill add a
/ / the parent component
const Father = () = > {
  const [count, setCount] = useState(0);
  return (
    <div className="app">
      <Son count={count}></Son>
      <Son></Son>
      <div onClick={()= > setCount((count) => count + 1)}>plus</div>
    </div>
  );
};
Copy the code
/ / child component
const Son: FC<ISon> = (props) = > {
  return <div>count is: {props.count ?? 0}</div>;
};
Copy the code

Install the React Chrome dev plugin and open update highlight. Click on the parent div and add count to one.

  1. The first oneSonHighlighted, meaning the component’srenderFunction fired
  2. The secondSonIt’s also highlighted, itsrenderFunctions are also fired

From the previous article, we knew that when props changed, render was triggered to re-execute, so the first Son update was made. But why is the second Son component also updated? Because the parent component render when the child component render

.

Wait, why are you taking it for granted? The second Son didn’t insert props, so why should the parent update it?

React.memo

React also thought about this problem. So it provides the React. Memo to solve this kind of problem. The component wrapped around it, if the props are not changed, will not follow the re-render parent. Let’s first look at the signature of this function:

It takes two arguments, the first a function component and the second a comparison function. The official website explains in more detail

  1. If your components are in the samepropsIn the case of rendering the same result, then useReact.memoReact then skips the render component and reuses the results of the last render
  2. This is only truepropsMake a judgment. There are subcomponentsstates,contextChanges will still trigger rerender
  3. The second argument is a comparison function that you can manually control when to block rendering

Let’s change the demo above and wrap the Son component with memo

export default React.memo(Son);
Copy the code

At this point we notice that highlight is normal and the second subcomponent that no props depends on is not highlighted

Oh, it’s easy. Just add a memo

.

Wait, why are you taking it for granted? Why doesn’t React do memo for all components by default?

Good question. We’ll leave that to the end of the article

One more minor detail: Notice that the Father component in the demo above is a div element. What happens if you kill it and replace it with <>? Give it a try and check out Highlight

useCallback

Let’s change the demo: it’s basically the same as above, but we put count plus one in the child component. When a child component is clicked, the count of the parent component is increased by one

/ / the parent component
const Father = () = > {
  const [count, setCount] = useState(0);
  const addCount = () = > setCount((count) = > count + 1);
  return (
    <div className="app">
      <Son addCount={addCount} count={count}></Son>
      <Son addCount={addCount}></Son>
    </div>
  );
};
Copy the code
/ / child component
const Son: FC<ISon> = (props) = > {
  return <div onClick={props.addCount}>count is: {props.count ?? 0}</div>;
};

export default memo(Son);
Copy the code

At that point, the memo is no longer valid. Even though the second Son component doesn’t use count, it reexecutes the Render function when it clicks on the child component (either the first or the second)

The reason is simple. The Memo compares props, and although the second component’s props does not accept count, it accepts a callback function

The memo’s props comparison is not the same as the callback that was last rendered

Now that you know what the problem is, try to tell Memo that the two CallBcaKs are actually the same guy. React uses the useCallback hook to solve this problem, which makes a persistent memory of a function, also known as a cache

// We just need to wrap addCount with useCallback, nothing else
// const addCount = () => setCount((count) => count + 1);
const addCount = useCallback(() = > setCount((count) = > count + 1), []);
Copy the code

And you’re done! The principle is also very simple, nothing more than to do a cache, in programming this is not unusual, in some recursive, backtracking and other algorithms can often see this design. For those interested, refer to the introduction of Memorization on Wikipedia.

Lodash also had a similar thing: [_. Memoize] (< https://www.lodashjs.com/docs/lodash.memoize >)

In short, in a word:useCallbackIt’s used to cache functions. The memo is used to compare functions, and the comparison is not accurate because of the reference typeuseCallbackPackage it and judge the cached result

useMemo

Let’s change the demo again:

  • The parent component only renders a child component this timeSonAnd an updated div
  • The parent component has oneuserObject that is passed to the child component for rendering
  • Click Update div to assignuserobject
/ / the parent component
const Father = () = > {
  const [user, setUser] = useState({ id: 1.name: "Shark" });
  const newUser = { id: 2.name: "Elephant" };
  const updateUser = () = > setUser(newUser);

  return (
    <div className="app">
      <Son user={user}></Son>
      <div onClick={updateUser}>setUser</div>
    </div>
  );
};
Copy the code
/ / child component
const Son: FC<ISon> = (props) = > {
  return (
    <div>
      <div>user id is: {props.user.id}</div>
      <div>user name is: {props.user.name}</div>
    </div>
  );
};

export default memo(Son);
Copy the code

When we click on div, even using memo doesn’t seem to help. React executes the render function every time you click on an update. If you’ve read all of this carefully, you should know what’s going on at this point without me telling you.

As in the useCallback example, the user object passed in is also a reference object, and memo can’t tell if it’s the same object as the last render object

Then tell React: Hey, this guy is the same guy! But we can’t use useCallback anymore, because useCallback is used to persist functions, and user is an object

React introduced another hook to solve this problem: useMemo, which works much like useCallback except that useMemo is used to cache values and useCallback is used for functions

// We just need to wrap newUser with useMemo, nothing else
// const newUser = { id: 2, name: "Elephant" };
const newUser = useMemo(() = > ({ id: 2.name: "Elephant" }), []);
Copy the code

Just to summarize a little bit

In fact, render optimization is not complicated, the core is before the implementation of render, to prejudge the “need to execute”. And the front action is react.Memo

The useCallback and useMemo brothers are more pure, they are used to cache functions and reference objects. In the memos scenario, though, the reference object in the props can’t be accurately identified.

  • withuseCallbackCaches functions passed to child components
  • withuseMemoCaches reference objects passed to child components
  • Then helpReact.memoMake a more accurate judgment on whether the props are changing

Of course, the useCallback and useMemo brothers are designed to cache things, not specifically serve the Memo. So if a function is complex or expensive to perform, it’s better to use them for caching because that’s what they should be doing

Q1: If there is no difference between the render function and the props before and after the update, do not execute the render function

Q2: What is useCallback? A2: Cache function. Often used to help the memo determine whether functions are equal

Q3: What is useMemo? A3: Cache values. This is often used to help the memo determine if reference types (such as objects and arrays) are equal

No silver bullet! Don’t abuse any React Memoize

About the React. Memo

Why doesn’t React make memo for all components by default?

Redux writer Dan (and now React core developer) had a Twitter thread about this, and another guy named Vjeux responded with this

I know you’ve got your hands on the keyboard, zuan maniac ready to go: Who are you, Dan?

The guy’s GitHub name is Christopher Chedeau. If you’re not familiar with him, he’s the creator or core developer of react-Native, Prettier, yoga…

If you’ve put your keyboard away, let me just briefly explain what he said, which basically means two things:

  1. React Diff saves a copy of the states. This overhead is always there with or without memos. So the claim that memo uses more memory is debatable
  2. If the props are too big, the structure is too complex, or if my component itself is too light, then doing the Memo might be counterproductive

The first point is easy to understand. React’s core is vDOM and diff. React: The memo is supposed to use up props. Vjeux said, “So it’s free.”

What about number two? Let’s do an example. Okay

// Suppose the props structure is complex and huge
const Son: FC<ISon> = (props) = > {
  return <div>{props}</div>;
};
Copy the code

Props is a simple component, without any logic, so let’s say the props structure is complex, such as a big tree

At this point, think about it. If you use memo, is it “reverse-optimized”?

  • If we domemo, a component inrenderBefore, we need to compare the props. This complex batch of props consumes a lot of time and performance
  • If we don’t do itmemo, the component does not need to compare props, just runrenderFunction. But in fact this component has no logic to render, so the render function has almost no burden.

So, there are thousands of components, and developers are very different. It is impossible to optimize from the framework layer to all cases anyway, and doing too much will even lead to negative optimization. React simply leaves it up to the developers to decide if the memo is ready

About useCallback and useMemo

First of all, any cache comes with a price, and so do these two brothers. They’re not silver bullets, right

Take useCallback for example. Imagine what would happen if we used useCallback to cache a function

const fn = () = > {
  // do something
}
const fnCallback = React.useCallback(fn, [])
Copy the code
  1. I created one firstfnThe function of
  2. Then you create onefnCallbackAnd executeuseCallbackCache the resultsfnCallback

You should note that every time the component renders, it will consume a react.usecallback (fn, []), which of course takes execution time. In addition, items cached by useCallback are not destroyed by garbage collection, and each callback takes up some memory space

So, if you have a useCallback by default, missing is good. UseMemo is similar

When exactly is it for

Then again, when do you use React. Memo, useCallback, useMemo?

  • Consider using the render function when it is expensive for the component to execute (e.g., every render requires some sort of computation)React.memoCaches the state of the component
  • When you useReact.memoWrap a subcomponent that has reference dependencies (including functions and reference objects) in its propsuseCallbackProcessing, for reference objects and arraysuseMemoTo deal with
  • When you have a particular piece of logic that is very complicated and expensive, you can use ituseMemoCaches the results of calculations

Of course, the above is only my superficial opinion, not what best practice, just share an optimization idea. Any engineering best practice takes time and experience to develop, as Linus Torvalds famously said:

“Talk is cheap. Show me the code.”