react减少组件不必要的重渲染

发布于:

#react 组件重渲染

在理解优化之前,先明确 什么情况下组件会重新渲染
  1. 组件自身的 state 变化 时,组件会重新执行 render
  2. 组件接收到的 props 发生变化 时,组件会重新执行 render
  3. 父组件重新渲染 时,子组件默认也会跟着重新渲染(即使 props 看起来没变)
前两点比较直观,第三点往往是性能问题的主要来源。
重渲染本身并不是坏事,React 的理念就是“多算少做 DOM”。 只有在 渲染逻辑复杂 / 组件树很深 / 高频更新 的情况下,才需要刻意优化。

#重渲染优化思路

优化的核心目标只有一个:
让不需要更新的组件,不参与这次 render
常见手段包括:
  • 缩小 state 的影响范围(组件提升 / 拆分组件)
  • 避免不必要的 props 变化(引用稳定)
  • 利用 React 提供的跳过渲染机制(memo / context 拆分)
下面按实际开发中最常用、最容易见效的方式展开。

#组件提升(Component Lifting)

#问题示例

tsx
function Page() { const [count, setCount] = useState(0) return ( <div> <button onClick={() => setCount(c => c + 1)}>+1</button> <HeavyComponent /> </div> ) }
每次点击按钮,Page 重新渲染,HeavyComponent 也会被迫重新渲染, 即使它和 count 完全无关

#优化方式:组件提升

将会频繁变化的 state 提升到 更小的组件中
tsx
function Counter() { const [count, setCount] = useState(0) return <button onClick={() => setCount(c => c + 1)}>+1</button> } function Page() { return ( <div> <Counter /> <HeavyComponent /> </div> ) }
这样:
  • Counter 自己更新自己
  • HeavyComponent 不会再因为计数变化而重渲染

#适用场景

  • 页面中有 局部频繁变化的状态
  • 父组件承担了过多 state
这是最简单、最推荐优先使用的优化方式

#使用 createContext 替换层层 props

#问题示例:props drilling

tsx
function App() { const [theme, setTheme] = useState('dark') return <Layout theme={theme} /> } function Layout({ theme }) { return <Content theme={theme} /> } function Content({ theme }) { return <div className={theme}>content</div> }
问题不止是代码冗余:
  • theme 变化时
  • 整条 props 传递链上的组件都会重新渲染

#使用 Context

tsx
const ThemeContext = createContext<'dark' | 'light'>('dark') function App() { const [theme, setTheme] = useState<'dark' | 'light'>('dark') return ( <ThemeContext value={theme}> <Layout /> </ThemeContext> ) } function Content() { const theme = use(ThemeContext) return <div className={theme}>content</div> }

#好处

  • 避免无意义的 props 透传
  • 只有 真正使用 context 的组件 会在 value 变化时重新渲染

#注意事项

  • Context 的 value 变化会导致所有消费它的组件重渲染
  • 不适合高频变化的数据(如鼠标位置、动画帧)
常见优化:
  • 拆分多个 context
  • context + memo 组合使用

#memo:跳过不必要的渲染

React.memo 可以让组件在 props 未变化时跳过重新渲染

#基本用法

tsx
const Child = React.memo(function Child({ value }: { value: number }) { console.log('render child') return <div>{value}</div> }) function Parent() { const [count, setCount] = useState(0) return ( <> <button onClick={() => setCount(c => c + 1)}>+1</button> <Child value={1} /> </> ) }
点击按钮时:
  • Parent 重新渲染
  • Child 不会重新渲染(props 没变)

#引用类型陷阱

tsx
<Child options={{ a: 1 }} />
每次 render,options 都是一个新对象,memo 会失效。
解决方式:
tsx
const options = useMemo(() => ({ a: 1 }), []) <Child options={options} />

#自定义比较函数(不推荐滥用)

tsx
React.memo(Component, (prevProps, nextProps) => { return prevProps.id === nextProps.id })
只有在:
  • props 结构复杂
  • 默认浅比较不满足需求
时才考虑使用。

#总结

优化 React 重渲染时,推荐的实践顺序:
  1. 先调整组件结构(组件拆分 / 提升)
  2. 再考虑 context 是否合适
  3. 最后才使用 memo / useMemo / useCallback
不要为了优化而优化。 当你在 React DevTools 里,明确看到“这个组件不该 render”时,再动手。

#额外:react compiler

react compiler 是 react17 及之后引入的一个新的优化手段,通过 babel 配置
它可以自动优化你的 React 应用,无需再使用 memo 和 callback
babel-plugin-react-compiler自动缓存组件
js
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [ react({ babel: { plugins: [ [ 'babel-plugin-react-compiler', // { // compilationMode: 'annotation', // }, ], ], }, }), ], })