【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进行记录,通过控制台确认后,两者都会被调用。
从这件事可以看出,当按下父组件中的按钮A时,父组件和子组件都被重新渲染。
无论在Child.js中使用memo来传递props,我们都可以看到子组件被重新渲染了。
理由是因为父组件的const clickHandler函数在每次父组件重新渲染时被重新定义,所以传递给的props中的clickHandler和在重新渲染时定义的const clickHandler函数是不同的东西。
コンポーネントA
const clickHandler = () => {・・・}
⇅ 再レンダリング前後で異なる関数
コンポーネントA(再レンダリング)
const clickHandler = () => {・・・}
因此,我们可以通过使用useCallback来解决上述问题,对clickHandler函数进行包裹。
const clickHandler = useCallback(() => {
setCountB((pre) => pre + 1);
},[])
在这里需要注意的是,必须向第二个参数传入一个空数组。
第二个参数被称为依赖数组。
当使用useCallback包裹了按钮A时,再次按下按钮A后,子组件将不再重新渲染。
通过使用useCallback,不需要每次定义一个函数,一旦传递给useCallback的函数将在React内部保存。
在重新渲染时,由于React内部返回了在ClickHandler函数中保持的函数,因此将使用最初定义的函数来进行循环执行。
在子组件中接收函数时,最好在父组件中使用useCallback包裹想要传递的函数。
关于第二个依赖数组
当包含在依存数组中的state的值被更新时,通过useCallback传递的函数会被React内部重写,从而可以使用最新的state的值。
如果将countA包含在第二个参数的依赖数组中,那么每当计数值发生变化时,函数都会在React内部被重新保持,这样在每次渲染时都会将不同的函数传递给子组件的props。
因此,按下“按钮A”将导致Child组件重新渲染。
const clickHandler = useCallback(() => {
setCountB((pre) => pre + 1);
},[countA])
即使在父组件中存在“按钮A”,也会重新渲染。
如果按下按钮A时不将countA包含在依存数组中
请参考