#react 组件重渲染
在理解优化之前,先明确 什么情况下组件会重新渲染:
- 组件自身的 state 变化 时,组件会重新执行 render
- 组件接收到的 props 发生变化 时,组件会重新执行 render
- 父组件重新渲染 时,子组件默认也会跟着重新渲染(即使 props 看起来没变)
前两点比较直观,第三点往往是性能问题的主要来源。
重渲染本身并不是坏事,React 的理念就是“多算少做 DOM”。 只有在 渲染逻辑复杂 / 组件树很深 / 高频更新 的情况下,才需要刻意优化。
#重渲染优化思路
优化的核心目标只有一个:
让不需要更新的组件,不参与这次 render
常见手段包括:
- 缩小 state 的影响范围(组件提升 / 拆分组件)
- 避免不必要的 props 变化(引用稳定)
- 利用 React 提供的跳过渲染机制(memo / context 拆分)
下面按实际开发中最常用、最容易见效的方式展开。
#组件提升(Component Lifting)
#问题示例
tsxfunction Page() { const [count, setCount] = useState(0) return ( <div> <button onClick={() => setCount(c => c + 1)}>+1</button> <HeavyComponent /> </div> ) }
每次点击按钮,
Page 重新渲染,HeavyComponent 也会被迫重新渲染,
即使它和 count 完全无关。#优化方式:组件提升
将会频繁变化的 state 提升到 更小的组件中:
tsxfunction 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
tsxfunction 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
tsxconst 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 未变化时跳过重新渲染。#基本用法
tsxconst 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 会失效。解决方式:
tsxconst options = useMemo(() => ({ a: 1 }), []) <Child options={options} />
#自定义比较函数(不推荐滥用)
tsxReact.memo(Component, (prevProps, nextProps) => { return prevProps.id === nextProps.id })
只有在:
- props 结构复杂
- 默认浅比较不满足需求
时才考虑使用。
#总结
优化 React 重渲染时,推荐的实践顺序:
- 先调整组件结构(组件拆分 / 提升)
- 再考虑 context 是否合适
- 最后才使用 memo / useMemo / useCallback
不要为了优化而优化。 当你在 React DevTools 里,明确看到“这个组件不该 render”时,再动手。
#额外:react compiler
react compiler 是 react17 及之后引入的一个新的优化手段,通过 babel 配置
它可以自动优化你的 React 应用,无需再使用 memo 和 callback
jsimport { 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', // }, ], ], }, }), ], })
