前言
无论是类组件还是函数式组件,React 性能优化的主要方向有以下两个:
-
减少组件的非必要的重新渲染
-
减少组件内部的重复计算
本文将介绍一些在平时开发中用的比较多的 React.memo、useMemo 和 useCallback......
React.memo
React.memo 是一个 高阶组件。
被 React.memo 包裹的组件在渲染前,会对新旧的 props 进行浅比较:
如果新旧
props浅比较相等,则不进行重新渲染(使用缓存的组件)。如果新旧
props浅比较不相等,则进行重新渲染(重新渲染的组件)。
在没有任何优化的情况下,React 中某一组件重新渲染,会导致其全部的子组件重新渲染。即通过 React.memo 的包裹,在其父组件重新渲染时,可以避免这个组件的非必要重新渲染。
注意:【渲染】指的是 React 执行函数组件并生成或更新虚拟 DOM 树(Fiber 树)的过程。在渲染真实 DOM (Commit 阶段)前还有 DOM Diff 的过程,会比对虚拟 DOM 之间的差异,再去渲染变化的 DOM。
useMemo
const memolized = useMemo(fn, deps);
useMemo 把计算函数 fn 和依赖项数组 deps 作为参数,useMemo 会执行 fn 并返回一个缓存值 memolized,它仅会在某个依赖项改变时才重新计算 memolized。这种优化有助于避免组件在每次渲染时都进行高开销的计算。
示例:
// 缓存计算 list 的和
const memoSum = useMemo(() => {
console.log("useMemo 计算");
return list.reduce((previous, current) => previous + current);
}, [list]);
在函数组件内部,一些基于 State 的衍生值和一些复杂的计算可以通过 useMemo 进行性能优化。
useCallback
const memolizedCallback = useCallback(fn, deps);
useCallback 把回调函数 fn 和依赖项数组 deps 作为参数,并返回一个缓存的回调函数 memolizedCallback (本质上是一个引用),它仅会在某个依赖项改变时才重新生成 memolizedCallback。当你把 memolizedCallback 作为参数传递给子组件(被 React.memo 包裹)时,它可以避免非必要的子组件重新渲染。
正确使用场景:
函数组件内部定义的函数需要作为其他
Hooks的依赖。函数组件内部定义的函数需要传递给其子组件,并且子组件由
React.memo包裹。
场景 1:useCallback 主要是为了避免当组件重新渲染时,函数引用变动所导致其它 Hooks 的重新执行,更为甚者可能造成组件的无限渲染:
import React, { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(1);
const add = () => {
setCount((count) => count + 1);
};
useEffect(() => {
add();
}, [add]);
return <div className="App">count: {count}</div>;
}
export default App;
上例中,useEffect 会执行 add 函数从而触发组件的重新渲染,函数的重新渲染会重新生成 add 的引用,从而触发 useEffect 的重新执行,然后再执行 add 函数触发组件的重新渲染...,从而导致无限循环。
为了避免上述的情况,我们给 add 函数套一层 useCallback 避免函数引用的变动,就可以解决无限循环的问题:
import React, { useCallback, useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(1);
// 用 useCallback 包裹 add ,只会在组件第一次渲染生成函数引用,之后组件重新渲染时,add 会复用第一次生成的引用。
const add = useCallback(() => {
setCount((count) => count + 1);
}, []);
useEffect(() => {
add();
}, [add]);
return <div className="App">count: {count}</div>;
}
export default App;
场景 2:useCallback 是为了避免由于回调函数引用变动,所导致的子组件非必要重新渲染。(这个子组件有两个前提:首先是接收回调函数作为 props,其次是被 React.memo 所包裹。
总结
通过
React.memo包裹组件,可以避免组件的非必要重新渲染。通过
useMemo,可以避免组件更新时所引发的重复计算。通过
useCallback,可以避免由于函数引用变动所导致的组件重复渲染。