在 React 开发中,组件的重新渲染是一个常见但需要谨慎处理的问题。当父组件的状态更新时,即使某些子组件的 props 没有变化,它们也会被重新渲染。这种不必要的渲染不仅浪费性能,还可能影响用户体验。
为了解决这个问题,React 提供了 React.memo 这个高阶组件(HOC),它可以对子组件进行浅层比较,避免在 props 没有变化时进行不必要的渲染。
一、React 组件的默认渲染机制
在开始讲解 React.memo 之前,我们先来看一下 React 组件的默认行为。
// src/App.jsx
import { useState } from 'react'
import Button from './components/Button'
function App() {
const [count, setCount] = useState(0)
const [number, setNumber] = useState(0)
return (
<>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>count增加</button>
<br />
<Button number={number} />
</>
)
}
// src/components/Button/index.jsx
const Button = ({ number }) => {
console.log('Button render')
return <button>{number}</button>
}
在这个例子中,当你点击 count增加 按钮时,App 组件会重新渲染,而 Button 组件也会重新渲染,即使它的 props 没有变化。
为什么?
因为 React 默认会在父组件重新渲染时,强制子组件也重新渲染,除非你明确告诉 React 不要这么做。
二、使用 React.memo 避免不必要的渲染
// src/components/Button/index.jsx
import { memo } from 'react'
const Button = ({ number }) => {
console.log('Button render')
return <button>{number}</button>
}
export default memo(Button)
这是
React.memo最基础也是最核心的用法。通过将组件包裹在memo中,React 会对组件的 props 进行浅比较,只有当 props 发生变化时,组件才会重新渲染。
在这个例子中,当你点击 count增加 按钮时,App 虽然会重新渲染,但 Button 不会,因为它的 number 没有变化。
三、结合 useCallback 避免函数引用变化导致的误触发
问题场景
// src/App.jsx
const handleClick = () => {
console.log('handleClick')
}
<Button number={number} onClick={handleClick} />
// src/components/Button/index.jsx
export default memo(Button)
虽然你使用了 React.memo,但 Button 依然会重新渲染。为什么?
因为每次 App 重新渲染时,handleClick 函数都会重新创建,导致 onClick 的引用发生变化,从而触发 Button 的更新。
使用 useCallback
// src/App.jsx
import { useCallback } from 'react'
const handleClick = useCallback(() => {
console.log('handleClick')
}, [number])
useCallback会缓存函数的引用,只有当依赖项变化时才会重新生成函数。结合React.memo使用,可以有效避免因函数引用变化而导致的子组件误渲染。
四、与 useMemo 配合:优化复杂计算值的传递
场景:传递一个复杂计算的值给子组件
// src/App.jsx
const expensiveComputation = (n) => {
console.log('expensiveComputation')
for (let i = 0; i < 100000; i++) {
i++
}
return n * 2
}
const result = expensiveComputation(number)
<Button number={result} />
在这个例子中,即使 number 没有变化,expensiveComputation 也会在每次渲染中执行,影响性能。
优化:使用 useMemo
const result = useMemo(() => expensiveComputation(number), [number])
useMemo会缓存计算结果,只有当依赖项number变化时才会重新计算,避免了每次渲染都执行耗时的逻辑。
五、组件拆分与性能优化的关系
拆分粒度建议
- 展示型组件:只负责渲染 UI,接收 props,不处理状态逻辑,适合使用
React.memo。 - 容器型组件:负责数据处理、状态管理,可以更频繁更新。
- 父子组件更新隔离:通过
React.memo和useCallback的组合,可以做到父子组件状态更新互不影响。
示例结构
App.jsx
├── 展示型组件:Button(使用 memo)
├── 展示型组件:Counter(使用 memo)
└── 容器型组件:App(处理状态)
合理的组件拆分不仅能提升代码可维护性,还能显著提升性能,特别是在大型应用中。
六、总结
- 基本用法:
export default memo(Component),避免无变化的 props 导致的渲染。 - 结合
useCallback:避免函数引用变化引发的误渲染。 - 结合
useMemo:避免复杂计算在每次渲染中重复执行。 - 合理拆分组件:展示型组件适合使用
memo,容器型组件可更灵活更新。 - 注意局限性:
memo只做浅比较,对于复杂对象或数组需要自定义比较函数。
在 React 开发中,性能优化是一个持续的过程,而 React.memo 是其中非常实用的工具之一。它能有效减少不必要的渲染,提升应用的响应速度和用户体验。