React组件渲染与重渲染
React 组件什么时候渲染、为什么重渲染,以及如何正确理解重渲染与性能问题的关系。
#frame
#react
#concept
#growing
[!info] related notes
- 所属 MOC: React MOC
- 上位主题: React
- 相关概念: useState, useMemo, useCallback, useRef, React Hooks
- 延伸阅读: react-performance-overview
React组件渲染与重渲染
理解 React,不能只停留在“状态变了页面就变了”,还要理解组件什么时候会重新执行,以及这和性能问题到底是什么关系。
一句话定义
React 组件重渲染,本质上是组件函数重新执行,得到新的 UI 描述,再由 React 比较差异并更新必要部分。
常见触发来源
- 组件自己的 state 变化
- 父组件重渲染并传入新的 props
- context 值变化
每次重渲染时到底发生了什么
- 组件函数重新执行
- Hook 按顺序重新调用
- React 从对应槽位取出旧状态
- 得到新的 JSX 结果
- React 比较前后差异
- 只提交必要的 DOM 更新
- 需要时再运行 effect cleanup 和新的 effect
这也是为什么 React 组件里的普通变量不会“自动记住上一次值”。
为什么 Hooks 总和重渲染绑在一起
因为每次渲染都会形成一套新的变量绑定和新的闭包。
function Demo() {
const [count, setCount] = useState(0)
function handleClick() {
console.log(count)
}
return <button onClick={handleClick}>{count}</button>
}
这里的 handleClick 拿到的是“这次渲染里的 count”,不是某个永恒不变的变量。
所以一旦你把回调交给定时器、订阅、异步流程,就很容易遇到闭包旧值问题。
一个很重要的事实
重渲染不等于性能问题。
真正可能昂贵的是:
- 大量子树无意义重复计算
- 大列表渲染
- 昂贵计算重复执行
- effect 和副作用反复运行
为什么很多人会误解它
因为“组件函数重新执行”听起来像很重,但 React 的模型本来就是声明式重算,问题通常出在状态设计和更新边界,而不是重渲染这个词本身。
和优化 Hook 的关系
useMemo主要优化计算结果复用useCallback主要优化函数引用稳定useRef用于保存不该触发重渲染的值
常见误解
- 以为函数组件只会创建一次
- 以为“函数重新执行”就等于 DOM 整体重建
- 以为只要 rerender 就一定要上
memo - 以为所有性能问题都来自 rerender 次数
真正更值得先查的是:
- 状态是不是放错位置了
- 是否建了不该有的派生状态
- 是否写了不必要的 Effect
- 是否有昂贵计算或长列表
最短记忆方式
先接受“重渲染是正常现象”,再去定位真正昂贵的是哪一段工作。
学习检查
不看正文回答:
- React 组件 rerender 与真实 DOM 更新为什么不是同一个事件?
- state、parent render、context 分别怎样触发或传导 render?
- 为什么函数组件每次 render 都会形成新的局部变量和闭包快照?
- “组件重新执行”为什么不等于“整个 DOM 重建”?
- 为什么 rerender 本身通常不是需要消灭的问题?
memo、useMemo、useCallback应该建立在哪些性能证据之上?- 给一个页面卡顿案例,先判断 render 次数、单次 render 成本、DOM commit、Effect、长列表中哪一项才是真瓶颈。