React 的执行规则主要围绕其核心机制:声明式 UI 渲染、虚拟 DOM 差分算法、组件生命周期 和 状态驱动更新。以下是 React 执行流程的关键规则和原理的详细说明:
1. 虚拟 DOM 与 Diff 算法
- 虚拟 DOM:React 通过 JavaScript 对象构建虚拟 DOM,与真实 DOM 解耦,减少直接操作 DOM 的性能开销。
- Diff 算法:当组件状态或 props 变化时,React 会重新生成虚拟 DOM,并通过 Diff 算法对比新旧虚拟 DOM 的差异,最终将差异高效地应用到真实 DOM 上(最小化操作)。
2. 组件执行流程
React 的执行分为两个阶段:渲染阶段(Reconciliation) 和 提交阶段(Commit) 。
渲染阶段(Reconciliation)
- 组件渲染:React 递归调用组件的
render方法(函数组件返回 JSX,类组件调用render()),生成新的虚拟 DOM。 - Fiber 架构:React 16 引入 Fiber 架构,将渲染工作拆分为小任务,支持中断和恢复(Concurrent Mode 的基础)。
- 副作用标记:在渲染阶段,React 会标记需要执行的副作用(如 DOM 操作、数据获取)。
提交阶段(Commit)
- 应用更新:React 将渲染阶段的结果一次性应用到真实 DOM。
- 执行副作用:调用生命周期方法(如
useEffect、componentDidMount)和事件回调。
3. 状态更新与异步性
-
不可变性原则:状态更新必须通过
setState或useState,不可直接修改状态。 -
异步批处理:React 会将多个状态更新合并为一次渲染(例如在事件处理或生命周期方法中),以提高性能。
javascript // 示例:多次 setState 会被合并 this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); // 最终只触发一次渲染 -
useEffect 的延迟执行:
useEffect在组件渲染完成后(提交阶段)执行,可能被延迟(尤其在 Concurrent Mode 下)。
4. 生命周期方法(类组件)
复制
| 阶段 | 生命周期方法 | 说明 |
|---|---|---|
| 挂载 | constructor → render → componentDidMount | 组件首次渲染完成 |
| 更新 | render → componentDidUpdate | 状态/props 变化后触发 |
| 卸载 | componentWillUnmount | 组件销毁前清理资源 |
5. 函数组件与 Hooks
-
Hooks 执行顺序:Hooks 必须在函数组件顶层按顺序调用(React 通过规则校验)。
-
useEffect:在每次渲染后执行,但可通过依赖项数组控制是否跳过。
javascript useEffect(() => { // 每次 count 变化时执行 }, [count]); -
useMemo/useCallback:惰性计算和缓存函数/值,避免重复渲染时的性能浪费。
6. Context 与 Provider
- Context 更新:当
Provider的 value 变化时,所有订阅该 Context 的子组件会重新渲染(即使未直接使用value)。 - 避免过度渲染:使用
React.memo或useMemo优化子组件性能。
7. 错误边界(Error Boundaries)
- 捕获错误:类组件可通过
componentDidCatch捕获子组件中的错误,防止整个应用崩溃。 - 函数组件限制:函数组件无法直接使用错误边界,需包裹在类组件中。
8. 并发模式(Concurrent Mode)
-
可中断渲染:React 18 引入的并发模式允许中断低优先级任务(如页面加载),优先处理高优先级任务(如用户输入)。
-
新 API:
useTransition:标记非紧急状态更新,允许用户界面保持响应。useDeferredValue:延迟更新非关键 UI 内容。
9. 性能优化规则
-
避免不必要的渲染:
- 使用
React.memo(函数组件)或PureComponent(类组件)进行浅比较。 - 合理使用
useMemo和useCallback缓存计算结果。
- 使用
-
Key 的重要性:列表渲染时为每个元素指定稳定的
key,帮助 React 正确识别变化。 -
懒加载组件:使用
React.lazy和Suspense动态加载组件。
10. 执行顺序示例
以一个简单组件为例:
javascript
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
- 初始渲染:调用
useState(0)初始化count,渲染按钮。 - 点击事件:触发
setCount(count + 1),React 将状态更新加入队列。 - 重新渲染:React 重新调用
Counter,生成新的虚拟 DOM。 - 提交阶段:更新 DOM 显示新
count,执行useEffect(更新页面标题)。
总结
React 的执行规则围绕 声明式更新 和 高效渲染 设计,核心逻辑是:
- 状态驱动视图:状态变化 → 重新渲染 → 虚拟 DOM 对比 → 更新真实 DOM。
- 异步与批处理:状态更新可能被合并,副作用延迟执行。
- 性能优化:通过缓存、避免重复渲染和并发模式提升性能。
理解这些规则后,可以更高效地开发 React 应用并解决常见问题(如无限循环更新、性能瓶颈)。
最后,总结一下React的基础执行规则
- 只在最顶层使用 Hook:不要在循环(for)、条件(if)或嵌套函数中调用 Hook,确保总是在你的 React 函数的 最顶层调用他们。遵守这条规则,就能确保 Hook 在每一次渲染中都按照同样的顺序被调用,这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
- 只在 React 组件或者其他自定义Hook函数中调用:不要在普通的 JavaScript 函数中调用 Hook。可以在 React的函数组件中调用 Hook,也可以在自定义 Hook 中调用其他 Hook。遵循此规则,可以确保组件的状态逻辑在代码中清晰可见。
以上就是 React Hooks 的基本执行规则。它们都是为了确保Hooks 的正确和一致的行,帮助开发者更好地管理和控制组件的状态和副作用。