preface

Reading this article requires a basic understanding of the React Hooks useState and useEffect. In this article, I outline the full use of Hooks in the React project.

useCallback

The role of useCallback

Official documents:

Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

In simple terms, return a function that is updated only if the dependencies change (return a new function).

The application of useCallback

Online Code: Code Sandbox

import React, { useState, useCallback } from 'react';
import Button from './Button';

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  const handleClickButton1 = () = > {
    setCount1(count1 + 1);
  };

  const handleClickButton2 = useCallback(() = > {
    setCount2(count2 + 1);
  }, [count2]);

  return (
    <div>
      <div>
        <Button onClickButton={handleClickButton1}>Button1</Button>
      </div>
      <div>
        <Button onClickButton={handleClickButton2}>Button2</Button>
      </div>
      <div>
        <Button
          onClickButton={()= > {
            setCount3(count3 + 1);
          }}
        >
          Button3
        </Button>
      </div>
    </div>
  );
}
Copy the code
// Button.jsx
import React from 'react';

const Button = ({ onClickButton, children }) = > {
  return (
    <>
      <button onClick={onClickButton}>{children}</button>
      <span>{Math.random()}</span>
    </>
  );
};

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

In this case, you can click on several buttons in the Demo to see the effect:

  • When clicking On Button1, only the content after Button1 and Button3 will be updated;
  • Clicking Button2 will update all three buttons;
  • Clicking on Button3 also updates only the content after Button1 and Button3.

Button1 and Button3 are the same in this case, but the function is written in a different place. Let’s take a closer look at the specific optimization logic.

You might notice the React. Memo method, which makes a shallow comparison of props and does not rerender the component if it has not changed.

const a = () => {};
const b = () => {};
a === b; // false
Copy the code

The above code can see that our two identical functions are not equal (this is nonsense, I’m sure anyone who can see this knows, so I won’t explain it).

const [count1, setCount1] = useState(0);
// ...
const handleClickButton1 = () = > {
  setCount1(count1 + 1);
};
// ...
return <Button onClickButton={handleClickButton1}>Button1</Button>
Copy the code

The Button component needs an onClickButton props. Although the component is optimized internally by react. memo, our declared handleClickButton1 defines a method directly. This will cause a new method to be declared as soon as the parent component is re-rendered. The new method looks the same as the old method, but it is still two different objects. React.

const handleClickButton2 = useCallback(() = > {
  setCount2(count2 + 1);
}, [count2]);
Copy the code

Our method is wrapped in useCallback and then passes a variable [count2], where useCallback decides whether to return a new function based on whether count2 has changed, and the internal scope of the function is updated accordingly.

Since our method relies only on count2, and count2 updates the handleClickButton2 only after Button2 is clicked, this causes us to click Button1 without rerendering the contents of Button2.

Tips

import React, { useState, useCallback } from 'react';
import Button from './Button';

export default function App() {
  const [count2, setCount2] = useState(0);

  const handleClickButton2 = useCallback(() = > {
    setCount2(count2 + 1); } []);return (
    <Button 
      count={count2}
      onClickButton={handleClickButton2}
    >Button2</Button>
  );
}
Copy the code

We adjusted the code to make the second parameter of the useCallback dependency an empty array, which means that the method has no dependency value and will not be updated. And because of the static scope of JS, count2 is always 0 in this function.

You can click Button2 multiple times to see the changes, and you’ll see that the value after Button2 changes only once. Because count2 in this function is always 0, which means 0 + 1 every time, the count props that Button accepts will only go from 0 to 1 and will always be 1, and handleClickButton2 won’t return a new method because it has no dependencies, Causes the Button component to be updated only once for a change in count.


That mentioned the problem of not updating, let’s take a look at the problem of frequent updates.

const [text, setText] = useState(' ');

const handleSubmit = useCallback(() = > {
  // ...
}, [text]);

return (
  <form>
    <input value={text} onChange={(e)= > setText(e.target.value)} />
    <OhterForm onSubmit={handleSubmit} />
  </form>
);
Copy the code

In the example above, you can see that our handleSubmit will depend on the update of the text, which must change quite frequently in the use of input, if our OhterForm is a large component, If you need to optimize, you can use useRef to help.

const textRef = useRef(' ');
const [text, setText] = useState(' ');

const handleSubmit = useCallback(() = > {
  console.log(textRef.current);
  // ...
}, [textRef]);

return (
  <form>
    <input value={text} onChange={(e)= >{ const { value } = e.target; setText(value) textRef.current = value; }} / ><OhterForm onSubmit={handleSubmit} />
  </form>
);
Copy the code

Using useRef, you can generate a variable that is accessible throughout the lifetime of the component, and handleSubmit does not update with text updates.


In the comments, some people mentioned whether to use useCallback for all methods. This should be a question for many friends who just know useCallback. The answer is: don’t include all the methods in useCallback.

const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);

const handleClickButton1 = () = > {
  setCount1(count1 + 1)};const handleClickButton2 = useCallback(() = > {
  setCount2(count2 + 1)
}, [count2]);

return (
  <>
    <button onClick={handleClickButton1}>button1</button>
    <button onClick={handleClickButton2}>button2</button>
  </>
)
Copy the code

When the current component is rerendered, it will declare a new handleClickButton1 function. The following function in useCallback will declare a new function, which will be passed to useCallback. Although it is possible that this function will not be returned to the handleClickButton2 variable because the inputs have not changed.

In this case, it returns the same function as the old one, because

UseCallback should be used in conjunction with the child component shouldComponentUpdate or React. Memo, otherwise it is a reverse optimization.

useMemo

The role of useMemo

Official documents:

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed.

In simple terms, you pass a create function and a dependency. The create function returns a value, and only if the dependency changes will the function be called again, returning a new value.

The application of useMemo

UseMemo is similar to useCallback, as you can already imagine from the previous useCallback that useMemo can also optimize the cache for incoming child component values.

// ...
const [count, setCount] = useState(0);

const userInfo = {
  // ...
  age: count,
  name: 'Jace'
}

return <UserCard userInfo={userInfo}>
Copy the code
// ...
const [count, setCount] = useState(0);

const userInfo = useMemo(() = > {
  return {
    // ...
    name: "Jace".age: count
  };
}, [count]);

return <UserCard userInfo={userInfo}>
Copy the code

Obviously the top userInfo is going to be a new object every time, whether or not the count changes, causing the UserCard to rerender, and the bottom is going to return the new object only after the count changes.

In fact, useMemo does more than that. According to the official documentation:

This optimization helps to avoid expensive calculations on every render.

You can put some expensive computational logic in useMemo and only update it when the dependent values change.

const num = useMemo(() = > {
  let num = 0;
  // Here we use count to do some complicated calculations for num. When count does not change, the component rerenders and returns the cached value directly.
  return num;
}, [count]);

return <div>{num}</div>
Copy the code

In fact, useMemo is much more widely used than useCallback. We can define the return value of useMemo as returning a function so that we can flexibly implement useCallback. In development, when we have a variable change that affects updates in multiple places, we can return an object or an array, and we can cache multiple data at the same time by deconstructing assignments.

const [age, followUser] = useMemo(() = > {
  return [
    new Date().getFullYear() - userInfo.birth, // Count age according to birthday
    async() = > {// Follow the user
      await request('/follow', { uid: userInfo.id });
      // ...}]; }, [userInfo]);return (
  <div>
    <span>name: {userInfo.name}</span>
    <span>age: {age}</span>
    <Card followUser={followUser}/>{useMemo(() => (// if the Card1 component does not use the React. Memo function internally, it is also possible to reduce the rendering of children in the parent component in this way<Card1 followUser={followUser}/>
      ), [followUser])
    }
  </div>
)
Copy the code

conclusion

UseCallback and useMemo cache both functions and returns. UseCallback is designed to optimize sub-components and prevent repeated rendering of sub-components. UseMemo can optimize the current component as well as sub-components, and the current component is optimized primarily by memoize to cache some complex computational logic. Of course, it is not necessary to use usemo for simple calculations, so consider the performance cost of comparing inputs (if there is a logic similar to this, there is no need to use usemo, and there is a little less keyboard wear 😅).

Leave a comment if you have any questions or suggestions.

The end.