React 组件渲染流程
React 组件从触发更新到页面渲染的完整过程
问题
React 组件是如何渲染到页面上的?更新时经历了哪些阶段?
解答
React 渲染分为两个主要阶段:Render 阶段和 Commit 阶段。
触发渲染的方式
// 1. 首次渲染
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// 2. 状态更新
const [count, setCount] = useState(0);
setCount(1); // 触发重新渲染
// 3. 父组件重新渲染
// 父组件渲染时,子组件默认也会重新渲染
Render 阶段(可中断)
这个阶段 React 会构建 Fiber 树,计算出需要更新的内容:
function Counter() {
const [count, setCount] = useState(0);
// 每次渲染,函数组件会重新执行
console.log('render'); // 状态变化时会打印
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Render 阶段的工作:
- 调用组件函数/render 方法,获取新的 JSX
- Diff 算法比较新旧虚拟 DOM
- 标记变更,记录需要执行的 DOM 操作
// React 内部会将 JSX 转换为虚拟 DOM 对象
// <div className="box">hello</div>
// 转换为:
{
type: 'div',
props: {
className: 'box',
children: 'hello'
}
}
Commit 阶段(不可中断)
将 Render 阶段计算出的变更应用到真实 DOM:
function Example() {
useEffect(() => {
// 3. useEffect 回调(异步执行)
console.log('useEffect');
});
useLayoutEffect(() => {
// 2. useLayoutEffect 回调(DOM 更新后同步执行)
console.log('useLayoutEffect');
});
// 1. 渲染
console.log('render');
return <div>Example</div>;
}
// 输出顺序:render -> useLayoutEffect -> useEffect
Commit 阶段分为三个子阶段:
- Before Mutation:执行
getSnapshotBeforeUpdate - Mutation:执行 DOM 操作(增删改)
- Layout:执行
useLayoutEffect,更新 ref
完整流程示例
import { useState, useEffect, memo } from 'react';
// 使用 memo 避免不必要的重新渲染
const Child = memo(function Child({ name }) {
console.log('Child render');
return <p>Hello, {name}</p>;
});
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
console.log('Parent render');
useEffect(() => {
console.log('Parent effect, count:', count);
}, [count]); // 依赖数组控制 effect 执行时机
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<Child name={name} /> {/* name 不变,Child 不会重新渲染 */}
</div>
);
}
// 点击按钮后输出:
// Parent render
// Parent effect, count: 1
// (Child 不会重新渲染,因为 props 没变)
类组件的生命周期对应
class MyComponent extends React.Component {
// Render 阶段
static getDerivedStateFromProps() {}
shouldComponentUpdate() {}
render() {}
// Commit 阶段
getSnapshotBeforeUpdate() {} // Before Mutation
componentDidMount() {} // Layout(首次)
componentDidUpdate() {} // Layout(更新)
componentWillUnmount() {} // Mutation(卸载时)
}
关键点
- 两阶段渲染:Render 阶段可中断(计算变更),Commit 阶段不可中断(操作 DOM)
- 触发条件:首次挂载、setState、props 变化、forceUpdate
- Fiber 架构:将渲染工作拆分为小单元,支持时间切片和优先级调度
- Diff 优化:同层比较、key 标识、类型判断,时间复杂度 O(n)
- 批量更新:React 18 默认对所有更新进行批处理,减少渲染次数
目录