【React】使用useCallback进行性能优化

这是关于React性能的备忘录。

为防止重新渲染的函数

    • React.memo

 

    • useCallback

 

    useMemo

React.memo可以用于优化React组件性能。

当接收到的props值相同时,检测state的差异并跳过重新渲染。如果用React.memo包围,在被包围的组件的props没有变化时,子组件将不会重新渲染。

如果不使用React.memo来包裹的话,无论props是否发生变化,都会导致整个组件重新渲染。

当函数传递给props时

在组件内定义的函数会在每次重新渲染时重新生成(重新定义)。
因此,尽管被React.memo包围,并且传递了props,它仍然会导致重新渲染。

コンポーネントA
const handleClick = ()=> {・・・}

⇅ 再レンダリング前後で異なる関数

コンポーネントA(再レンダリング)
const handleClick = ()=> {・・・}

只有使用React.memo无法阻止重新渲染。

解决这个问题的方式是…

使用useCallback

在组件内定义的“函数”,可以记住并重复使用,从而防止在渲染时被多次生成。

如果在子组件中传递函数,则可以防止不必要的重新渲染。

因此,如果对该函数没有进行任何更改,React.memo功能将被激活,从而防止子组件进行不必要的重新渲染。

除了组件之外,还可以如何缓存各种数值呢?

使用useMemo

除了组件之外,还可以存储值。可以对耗费较高的处理过程进行记忆处理。
然而,由于执行useMemo本身也需要成本,所以只应在繁重的处理过程中使用。

※不应该将所有东西都用useMemo进行优化
┗ 由于处理本身也有计算成本,所以仅在重型处理中使用。

通过阻止组件重新渲染来提高性能。

进一步学习有关useCallback的内容。

useCallback是React Hook,它可以在重新渲染期间缓存函数定义。

使用`useCallback(fn, dependencies)`

函数参数:缓存函数的值。可以接收任意的参数并返回任意的值。在React的首次渲染中,它将返回函数(不是调用!)。在下一次渲染时,如果上一次渲染后没有变化,React将再次提供相同的函数。否则,它将提供在当前渲染中传递的函数并将其保存以便以后重用。React不会直接调用函数。它会返回函数,因此您可以决定何时调用它或是否调用它。

依存项: fn 是一个列表,包含在代码中被引用的所有响应式值。响应式值包括 props、state 和组件内直接声明的所有变量和函数。如果使用的是配置为 React 的 linter,将验证所有响应式值是否正确指定为依赖关系。依赖关系列表必须包含一定数量的项,并需要以内联方式进行描述[dep1, dep2, dep3]。React 使用比较算法将每个依赖项与先前的值进行比较,使用的算法为 Object.is。

React公式文档的含义。

用简单的代码进行解释。

(親コンポーネント)
import React, { useState } from "react";
import Child from "./Child";

const Example = () => {
  console.log("Parent render");
  
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

  const clickHandler = () => {
    setCountB((pre) => pre + 1);
  }

  return (
    <div className="parent">
      <div>
        <h3>親コンポーネント領域</h3>
        <div>
          <button
            onClick={() => {
              setCountA((pre) => pre + 1);
            }}
          >
            ボタンA
          </button>
          <span>親のstateを更新</span>
        </div>
      </div>
      <div>
          <p>ボタンAクリック回数{countA}</p>
      </div>
      <Child countB={countB} onClick={clickHandler}/>
    </div>
  );
};

export default Example;
(子コンポーネント)
import { memo } from "react";

const ChildMemo = memo(({ countB, onClick }) => {
  console.log("%cChild render", "color: red;");

  return (
    <div className="child">
      <h2>子コンポーネント領域</h2>
      <div>
        <button
          onClick={onClick}
        >
          ボタンB
        </button>
        <span>子のpropsに渡すstateを更新</span>
      </div>
      <span>ボタンBクリック回数{countB}</span>
    </div>
  );
});

export default ChildMemo;

在父组件中有按钮A。
在子组件中有按钮B,并且在父组件中定义了按下按钮时的行为函数。

使用props将onClick传递。

按下按钮A后,状态将会被更新。
在父组件和子组件中都使用console.log进行记录,通过控制台确认后,两者都会被调用。

undefined

从这件事可以看出,当按下父组件中的按钮A时,父组件和子组件都被重新渲染。

无论在Child.js中使用memo来传递props,我们都可以看到子组件被重新渲染了。

スクリーンショット 2023-08-31 14.36.16.png

理由是因为父组件的const clickHandler函数在每次父组件重新渲染时被重新定义,所以传递给的props中的clickHandler和在重新渲染时定义的const clickHandler函数是不同的东西。

コンポーネントA
const clickHandler = () =>  {・・・}

⇅ 再レンダリング前後で異なる関数

コンポーネントA(再レンダリング)
const clickHandler = () =>  {・・・}

因此,我们可以通过使用useCallback来解决上述问题,对clickHandler函数进行包裹。

  const clickHandler = useCallback(() => {
    setCountB((pre) => pre + 1);
  },[])

在这里需要注意的是,必须向第二个参数传入一个空数组。
第二个参数被称为依赖数组。

当使用useCallback包裹了按钮A时,再次按下按钮A后,子组件将不再重新渲染。

undefined

通过使用useCallback,不需要每次定义一个函数,一旦传递给useCallback的函数将在React内部保存。

在重新渲染时,由于React内部返回了在ClickHandler函数中保持的函数,因此将使用最初定义的函数来进行循环执行。

在子组件中接收函数时,最好在父组件中使用useCallback包裹想要传递的函数。

关于第二个依赖数组

当包含在依存数组中的state的值被更新时,通过useCallback传递的函数会被React内部重写,从而可以使用最新的state的值。

如果将countA包含在第二个参数的依赖数组中,那么每当计数值发生变化时,函数都会在React内部被重新保持,这样在每次渲染时都会将不同的函数传递给子组件的props。

因此,按下“按钮A”将导致Child组件重新渲染。

  const clickHandler = useCallback(() => {
    setCountB((pre) => pre + 1);
  },[countA])
undefined

即使在父组件中存在“按钮A”,也会重新渲染。

如果按下按钮A时不将countA包含在依存数组中

undefined

请参考

 

广告
将在 10 秒后关闭
bannerAds