⚛️ React 全流程深度解析
React 以其声明式编程、组件化和高效的 DOM 更新机制,彻底改变了现代前端开发。但你是否曾好奇,从你写下 <MyComponent />
到用户在屏幕上与之交互,这背后究竟发生了一场怎样精密而高效的"演出"?
本文旨在为你揭开 React 的神秘面纱,完整地梳理一个 React 应用从源代码到最终在浏览器中呈现并响应用户交互的全流程。无论你是希望巩固基础的 React 新手,还是寻求更深层次理解的资深开发者,这篇文章都将带你领略 React 工作流的每个关键环节。
我们将一起探索:
- 开发与构建阶段: JSX 如何变成浏览器能理解的代码?
- 浏览器加载阶段: HTML 和 JavaScript 如何被加载和执行?
- 首次渲染(Mount)阶段: React 如何将组件"绘制"到屏幕上?
- 状态更新与触发:
setState
或useState
如何启动更新流程? - 重新渲染与协调(Reconciliation)阶段: React 如何高效地更新变化的部分?
- 卸载(Unmount)阶段: 组件消失时会发生什么?
- 现代特性概览: 并发模式、SSR/Streaming 等如何融入流程?
React 生态系统与包结构
在深入流程之前,让我们先了解 React 的核心包结构。React 采用模块化设计,由多个专注于特定功能的包组成。
核心包及其职责:
- react: 定义核心API,如React.createElement、Hooks等,但不含具体平台渲染逻辑
- react-dom: 针对浏览器环境的渲染器,将React组件渲染为DOM
- react-native: 针对移动平台的渲染器,将React组件渲染为原生视图
- react-reconciler: 实现协调算法,构建和比较Fiber树
- scheduler: 调度系统,根据优先级调度不同的工作
- react-events: 管理合成事件系统
阶段一:编写代码与构建打包
一切始于开发者编写的代码。
- 编写组件与 JSX:
- 开发者使用 JavaScript (或 TypeScript) 编写 React 组件(函数组件或类组件)。
- 使用 JSX 这种类似 HTML 的语法糖来声明式地描述 UI 结构。
// src/MyComponent.js
import React, { useState } from 'react';
function MyComponent({ message }) {
const [count, setCount] = useState(0);
return (
<div className="my-component">
<h1>{message}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
export default MyComponent;
- 编译转换 (Transpilation):
- 由于浏览器不认识 JSX 和可能使用的现代 JavaScript 语法(如 ES6+),我们需要编译器(通常是 Babel)介入。
- Babel 将 JSX 转换为
React.createElement()
函数调用。 - 同时,它也会将 ES6+ 语法转换为浏览器兼容性更好的 ES5 语法(根据配置)。
// JSX 转换示例 (概念性)
// <h1 className="greeting">Hello</h1>
// 编译为 ->
React.createElement('h1', { className: 'greeting' }, 'Hello');
- 模块打包 (Bundling):
- 现代前端项目通常由许多模块(JS 文件、CSS 文件、图片等)组成。
- 打包工具(如 Webpack, Vite, Parcel, Rollup)会分析项目依赖关系,将所有需要的模块打包成一个或多个优化过的静态资源文件(通常称为 Bundles),主要是 JavaScript 和 CSS 文件。
- 打包过程还可能包括代码压缩、混淆、代码分割(Code Splitting)、Tree Shaking(移除未使用的代码)等优化步骤。
产出: 最终生成 index.html 文件以及一个或多个 JavaScript 和 CSS 文件(Bundles),准备部署到服务器。
阶段二:浏览器加载与执行
用户访问应用时,浏览器开始工作。
- 请求 HTML: 浏览器向服务器请求入口 index.html 文件。
- 解析 HTML: 浏览器接收并开始解析 HTML。遇到
<link rel="stylesheet" href="...">
时,会异步下载 CSS 文件。遇到<script src="...">
时(如果不是 defer 或 async),会暂停 HTML 解析,下载并执行 JavaScript。现代实践通常将<script>
标签放在<body>
底部或使用 defer 属性,以避免阻塞页面渲染。 - 下载资源: 浏览器下载 HTML 中引用的 CSS 和 JavaScript Bundles。
- 构建 DOM 和 CSSOM: 浏览器解析 HTML 构建 DOM (Document Object Model) 树,解析 CSS 构建 CSSOM (CSS Object Model) 树。两者结合形成 Render Tree(渲染树)。
- 执行 JavaScript: 下载完成后,浏览器执行 JavaScript Bundles。React 的代码开始运行!
阶段三:首次渲染(Mount)- React接管UI
这是 React 展示其魔法的第一个关键阶段。通常由入口文件(如 src/index.js)中的代码启动。
- 调用
createRoot().render()
:
- React 18+ 的标准入口方式。
ReactDOM.createRoot(container)
选择一个 DOM 容器节点,并创建一个 React 根。 root.render(<App />)
指示 React 将<App />
组件渲染到这个根中
// src/index.js (或类似入口文件)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'; // 根组件
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
- 创建 React Element 树 (Virtual DOM):
render()
函数接收到<App />
这个React Element
。- React 开始调用组件(如 App 函数)并执行其代码。
- 组件返回 JSX,通过
React.createElement()
(或编译器优化后的 jsx() 函数)创建描述 UI 结构的 React Element 对象树。这就是我们常说的 Virtual DOM 的一种内存表示。它只是普通的 JavaScript 对象,非常轻量。
- 进入协调阶段 (Reconciliation) - 构建 Fiber 树:
- Fiber 架构: React 使用称为 Fiber 的内部架构来管理渲染工作。Fiber 节点不仅包含了组件类型、props、state 等信息,还维护了组件之间的关系(child, sibling, return 指针),并作为可中断、可恢复的工作单元。
- 首次挂载: React 从根节点开始,遍历 React Element 树,为每个 Element 创建一个对应的 Fiber 节点,构建起第一个 Fiber 树 (Work-in-Progress Tree)。这个过程发生在 Render 阶段。
- 在此过程中,如果是类组件,会创建实例;如果是函数组件,会执行函数。
- 提交阶段 (Commit):
- 当整个 Fiber 树构建(或更新)完成后,React 进入 Commit 阶段。这个阶段是同步且不可中断的。
- DOM 操作: React 根据 Fiber 树的信息,计算出需要执行的所有 DOM 操作(因为是首次渲染,主要是创建节点、设置属性)。
- 真实 DOM 构建: React 调用浏览器 DOM API (如 document.createElement, appendChild, setAttribute 等) 将 Fiber 节点映射为真实的 DOM 节点,并将它们插入到 createRoot 指定的容器中。
- 生命周期/Effect 执行:
- componentDidMount (类组件) 被调用。
- useLayoutEffect 的回调函数同步执行。
- useEffect 的回调函数被调度在稍后(浏览器绘制之后)异步执行。
- ref 被附加到对应的 DOM 节点或类组件实例上。
阶段四:状态更新与触发
React 应用是动态的,需要响应用户交互或其他事件。当状态发生变化时,React 需要高效地更新 UI。
更新触发方式:
- 类组件:
this.setState()
- 函数组件:
useState
的 setter 函数、useReducer
的 dispatch - Context:
Context.Provider
的 value 变化 - 强制更新:
forceUpdate()
或 reducer 返回完全相同的状态
- 类组件:
事件处理系统:
- React 实现了合成事件(Synthetic Events)系统,它是原生 DOM 事件系统的跨浏览器包装器
- 事件委托: React 会把几乎所有事件都委托到 document 级别(React 17+改为 root 容器级别)
- 事件处理程序中调用的状态更新会触发重新渲染
阶段五:重新渲染与协调 - 高效更新
当状态更新后,React 会启动重新渲染流程。这一流程与首次渲染类似,但包含一个关键差异:React 会比较新旧两棵树,只更新必要的部分。
启动更新:
- 状态更新会被加入队列,并触发调度器进行调度
- 根据更新的优先级决定何时处理(并发特性)
Render 阶段:
- 重新执行对应组件的渲染函数,生成新的 React Elements 树
- 创建一棵新的 "work-in-progress" Fiber 树
- 执行 协调算法 (Reconciliation) 来比较当前Fiber树与新的Elements树
Diffing 算法:
- 基于两个假设:
- 不同类型的元素会产生不同的树
- 开发者可以通过
key
属性暗示哪些子元素在不同渲染中保持稳定
- 逐层比较:
- 不同类型的元素: 拆除旧树,建立新树
- 相同类型的DOM元素: 只更新改变的属性
- 相同类型的组件元素: 更新props,并递归继续比较
- 列表diffing: 使用key高效识别移动、添加或删除的项
- 基于两个假设:
Commit 阶段:
- 与首次渲染类似,但只对需要更新的部分执行DOM操作
- 可能涉及的操作:
- 更新现有DOM节点属性
- 插入新DOM节点
- 移动或删除现有DOM节点
- 执行更新相关的生命周期/Effect:
- componentDidUpdate
- useLayoutEffect (同步执行)
- useEffect (稍后异步执行)
阶段六:卸载(Unmount)阶段
在某些情况下,组件需要从UI中移除。例如:
- 条件渲染的条件改变
- 组件被从列表中移除
- 用户导航到另一个页面
- 整个应用程序关闭
卸载过程:
触发卸载:
- 父组件决定不再渲染某个子组件
- 在Diffing过程中,React发现某个元素不再存在
清理工作:
- 递归处理组件树,对每个需要卸载的组件:
- 类组件: 调用
componentWillUnmount
生命周期 - 函数组件: 执行
useEffect
返回的清理函数
- 类组件: 调用
- 解除引用,避免内存泄漏
- 移除事件监听器和订阅
- 递归处理组件树,对每个需要卸载的组件:
DOM移除:
- 在commit阶段,从DOM中移除对应的节点
- 移除与该节点相关的所有事件处理程序
阶段七:现代特性概览
React 不断发展,引入了许多现代特性以提升性能和开发体验。
并发模式 (Concurrent Mode)
React 18 引入的最重要特性,使 React 能够准备多个版本的 UI,而不阻塞主线程。
核心机制:
- 时间切片: 将长任务分割成小片段执行,每执行一小段就检查是否有高优先级工作
- 优先级系统: 不同的更新有不同的优先级
- Urgent: 需要同步处理(如用户输入)
- High: 用户交互(如点击)
- Normal: 普通更新
- Low: 可以延迟的工作(如屏幕外数据)
- 启用方式: 使用
createRoot
(React 18 默认)
服务端渲染(SSR)与流式渲染
- 传统SSR: 服务器生成完整HTML,客户端加载React后进行hydration
- 流式SSR (React 18):
renderToPipeableStream
: 服务器生成HTML流,可以逐步发送- 与Suspense集成,允许部分UI先加载,部分等待数据
- Selective Hydration: 用户交互的部分优先激活
其他关键特性
- Automatic Batching: 自动批处理多个状态更新,减少渲染次数
- Transitions: 将UI更新标记为非紧急,允许保持当前UI响应
- Suspense for Data Fetching: 声明式地指定加载状态
- Server Components: 只在服务器运行、不增加客户端bundle大小的组件
总结: React工作流全景图
以下是React从代码到屏幕的完整流程总览:
本文提供了React工作流程的高层次理解。在后续文章中,我们将深入探讨各个关键环节的实现细节,包括:
- Fiber架构深度解析
- 协调算法的实现机制
- Hook原理与内部实现
- 并发模式的技术细节
- 服务端渲染的全流程
- 性能优化技巧与最佳实践
通过理解React的工作原理,你将能够更有效地使用它,编写更高效、更可维护的代码,并更好地解决实际开发中遇到的问题。