React.memo 的性能优化

55 阅读4分钟

在 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.memouseCallback 的组合,可以做到父子组件状态更新互不影响。

示例结构

App.jsx
├── 展示型组件:Button(使用 memo)
├── 展示型组件:Counter(使用 memo)
└── 容器型组件:App(处理状态)

合理的组件拆分不仅能提升代码可维护性,还能显著提升性能,特别是在大型应用中。


六、总结

  1. 基本用法export default memo(Component),避免无变化的 props 导致的渲染。
  2. 结合 useCallback:避免函数引用变化引发的误渲染。
  3. 结合 useMemo:避免复杂计算在每次渲染中重复执行。
  4. 合理拆分组件:展示型组件适合使用 memo,容器型组件可更灵活更新。
  5. 注意局限性memo 只做浅比较,对于复杂对象或数组需要自定义比较函数。

在 React 开发中,性能优化是一个持续的过程,而 React.memo 是其中非常实用的工具之一。它能有效减少不必要的渲染,提升应用的响应速度和用户体验。