阅读 688

详解 useCallback & useMemo

前言

阅读本文章需要对 React hooksuseStateuseEffect 有基础的了解。我的这篇文章内有大致介绍 在 React 项目中全量使用 Hooks

useCallback

useCallback 的作用

官方文档:

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.

简单来说就是返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)。

useCallback 的应用

在线代码: 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 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.jsx
import React from 'react';

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

export default React.memo(Button);
复制代码

在案例中可以点击 Button1 和 Button2 两个按钮来查看效果,点击 Button1 的时候只会更新 Button1 后面的内容,点击 Button2 会将两个按钮后的内容都更新。这就表示我在点击 Button2 的时候导致了两个按钮内都重新渲染了。

这里或许会注意到 React.memo 这个方法,此方法内会对 props 做一个浅层比较,如果如果 props 没有发生改变,则不会重新渲染此组件。

const a = () => {};
const b = () => {};
a === b; // false
复制代码

上述代码可以看到我们两个一样的函数却是不相等的(这是个废话,我相信能看到这的人都知道,所以不做解释了)。

const [count1, setCount1] = useState(0);
// ...
const handleClickButton1 = () => {
  setCount1(count1 + 1);
};
// ...
return <Button onClickButton={handleClickButton1}>Button1</Button>
复制代码

回头再看上面的 Button 组件都需要一个 onClickButton 的 props ,尽管组件内部有用 React.memo 来做优化,但是我们声明的 handleClickButton1 是直接定义了一个方法,这也就导致只要是父组件重新渲染(状态或者props更新)就会导致这里声明出一个新的方法,新的方法和旧的方法尽管长的一样,但是依旧是两个不同的对象,React.memo 对比后发现对象 props 改变,就重新渲染了。

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

上述代码我们的方法使用 useCallback 包装了一层,并且后面还传入了一个 [count2] 变量,这里 useCallback 就会根据 count2 是否发生变化,从而决定是否返回一个新的函数,函数内部作用域也随之更新。

由于我们的这个方法只依赖了 count2 这个变量,而且 count2 只在点击 Button2 后才会更新 handleClickButton2,所以就导致了我们点击 Button1 不重新渲染 Button2 的内容。

注意点

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>
  );
}
复制代码

我们调整了一下代码,将 useCallback 依赖的第二个参数变成了一个空的数组,这也就意味着这个方法没有依赖值,将不会被更新。且由于 JS 的静态作用域导致此函数内 count2 永远都 0

可以点击多次 Button2 查看变化,会发现 Button2 后面的值只会改变一次。因为上述函数内的 count2 永远都是 0,就意味着每次都是 0 + 1,Button 所接受的 count props,也只会从 0 变成 1且一直都将是 1,而且 handleClickButton2 也因没有依赖项不会返回新的方法,就导致 Button 只会因 count 改变而更新一次后就不会被重新渲染。

useMemo

useMemo 的作用

官方文档:

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

简单来说就是传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的依赖值。

useMemo 的应用

useMemo 与 useCallback 很像,根据上述 useCallback 已经可以想到 useMemo 也能针对传入子组件的值进行缓存优化,当然这个值必须是一个对象,如果不是对象而是一些简单类型的如字符串等,那么没更改 React.memo 也能对比出来,下面就直接举个 🌰 对比一下。

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

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

return <UserCard userInfo={userInfo}>
复制代码
// ...
const [count, setCount] = useState(0);

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

return <UserCard userInfo={userInfo}>
复制代码

很明显的上面的 userInfo 每次都将是一个新的对象,无论 count 发生改变没,都会导致 UserCard 重新渲染,而下面的则会在 count 改变后才会返回新的对象。

实际上 useMemo 的作用不止于此,根据官方文档内介绍,它主要的功能应该是:

This optimization helps to avoid expensive calculations on every render.

可以吧一些昂贵的计算逻辑放到 useMemo 中,只有当依赖值发生改变的时候才去更新。

const num = useMemo(() => {
  let num = 0;
  // 这里使用 count 针对 num 做一些很复杂的计算,当 count 没改变的时候,组件重新渲染就会直接返回之前缓存的值。
  return num;
}, [count]);

return <div>{num}</div>
复制代码

也能在很多情况将两种情况结合起来用。

结束。

关注下面的标签,发现更多相似文章
评论