在日常 React 开发和面试中,我们经常面临一个普遍的问题:组件的不必要重新渲染。这不仅会影响应用性能,还可能导致不必要的资源浪费。在本文中,我们将通过一个简单的实例来演示在日常开发中可能遇到的问题,讨论如何运用 React.memo
、useMemo
和 useCallback
来优化组件性能,从而避免不必要的重新渲染。
日常开发场景
假设我们正在开发一个计数器应用,用户可以通过点击按钮分别增加两个计数器的数值。同时,我们希望在某些特定条件下触发一些状态的变化。下面是一个简化的代码示例:
import React, { useState } from 'react';
const App = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [active, setActive] = useState(false);
const increaseCounter = () => {
setCount1(count1 + 1);
// 需要在某些条件下触发状态变化
if (count2 > 5) {
setActive(true);
}
}
return (
<>
<button onClick={increaseCounter}>Increase counter</button>
<div>Counter 1: {count1}</div>
<div>Counter 2: {count2}</div>
{active && <div>特定条件已触发</div>}
</>
);
}
export default App;
在这个简单的例子中,存在潜在的性能问题:
- 频繁的重新渲染: 每次增加计数器时,都会导致
App
组件重新渲染,即使只有count1
发生了变化。 - 性能开销: 随着应用规模的增大,不必要的重新渲染会导致性能开销的增加。
引入 React.memo 优化:函数组件的记忆化
为了避免不必要的重新渲染,我们可以使用 React.memo
对子组件进行优化。这个工具通过浅层比较 props 的方式来实现,确保只有在相关数据发生变化时才触发组件的重新渲染。在下面的例子中,我们演示如何使用 React.memo
对 count2
组件进行优化:
// Counter 组件
const Counter = ({ value, children }) => {
console.log('Render: ', children);
return (
<div>
{children}: {value}
</div>
);
}
export default React.memo(Counter);
经过这一步优化后,我们发现只有 count1
在每次点击按钮后重新渲染,而 count2
不再频繁重新渲染。这是因为 React.memo
在浅比较 props
的情况下,只有在 count2
发生变化时才重新渲染。
若涉及到复杂的数据结构,我们还可以通过 React.memo
的第二个参数传递自定义的比较函数,实现深层比较。
const Counter = ({ value, children }) => {
console.log('Render: ', children);
return (
<div>
{children}: {value}
</div>
);
}
const isEqual = (prevProps, nextProps) => {
if (prevProps.name !== nextProps.name) {
return false;
}
return true;
}
export default React.memo(Counter, isEqual);
进一步优化:useMemo 和 useCallback
在上述例子中,我们只是解决了 count2
组件的重新渲染问题。然而,我们还可以在组件中应用更深层次的优化。假设我们希望在点击按钮时,还能显示一个文字提示,告诉用户按钮是否已被点击。为了避免不必要的计算,我们可以使用 useMemo
来记忆化计算结果,同时使用 useCallback
记忆化回调函数。
const App = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [active, setActive] = useState(false);
// 使用 useMemo 记忆化计算结果
const shouldActivate = React.useMemo(() => count2 > 5, [count2]);
// 使用 useCallback 记忆化回调函数
const increaseCounter = React.useCallback(() => {
setCount1(count1 + 1);
// 只有在特定条件下才触发状态变化
if (shouldActivate) {
setActive(true);
}
}, [count1, shouldActivate]);
return (
<>
<button onClick={increaseCounter}>Increase counter</button>
<Counter value={count1}>Counter 1</Counter>
<Counter value={count2}>Counter 2</Counter>
{active && <div>特定条件已触发</div>}
</>
);
}
在这个例子中,我们使用了 useMemo
来记忆化了基于 count2
计算的结果,并使用 useCallback
记忆化了基于count1
、shouldActivate
的回调函数。
总结
我们先看一个表格总结,这表格总结了 React.memo
、useMemo
和 useCallback
的主要特性。
特性 | React.memo | useMemo | useCallback |
---|---|---|---|
用途 | 避免不必要的重新渲染 | 记忆化计算结果 | 记忆化回调函数 |
适用场景 | 函数组件 | 记忆化计算结果 | 记忆化回调函数 |
参数 | 一个组件 | 一个计算函数 和依赖项数组 |
一个回调函数 和依赖项数组 |
返回值 | 一个记忆化的组件 | 一个记忆化的值 | 一个记忆化的回调函数 |
作用方式 | 对比 props 的变化 | 对比依赖项数组的变化 | 对比依赖项数组的变化 |
下面我们看下具体的区别和示例,加深理解~
React.memo
- 用途:用于函数组件,通过对比组件的 props 变化来避免不必要的重新渲染。
- 工作原理:
React.memo
对比组件接收到的props
是否发生变化。仅当props
发生变化时,才会重新渲染组件,否则会使用上一次的渲染结果。 - 示例:
const memoComponent = React.memo(MyComponent);
useMemo
- 用途:用于记忆化计算结果,避免在每次渲染时都重新计算。
- 工作原理:
useMemo
接收一个计算函数和一个依赖项数组。它仅在依赖项数组中的值发生变化时,才会重新计算,并返回记忆化的值。 - 示例:
const useMemoValue = useMemo(() => computeExpensiveValue(dep1, dep2), [dep1, dep2]);
useCallback
- 用途:用于记忆化回调函数,避免在每次渲染时都重新创建回调。
- 工作原理:
useCallback
接收一个回调函数和一个依赖项数组。它仅在依赖项数组中的值发生变化时,才会返回上一次的记忆化的回调函数。 - 示例:
const useCallbackValue = useCallback(() => { // 回调逻辑 }, [dep1, dep2]);
注意:useMemo是计算函数,useCallback是回调函数。
收尾
通过 React.memo
、useMemo
和 useCallback
的运用,我们能够有效地降低组件的重新渲染次数,确保组件只在必要时进行重新渲染,提升应用的性能表现。希望这篇文章,对你有所帮助!
原文链接:https://juejin.cn/post/7316321509856755752 作者:Sailing