题记:理解 React 为什么这样设计,要先从它运行的土壤说起。
🔍 本节要点
- 现代浏览器不止一个进程
- 渲染进程是 React 的"主战场"
- 多进程如何影响 React 的渲染策略
浏览器不只是"一个程序"
很多人写 JavaScript 的时候,默认浏览器是一个黑盒。但实际上,从 Chrome 57 开始,Chromium 内核就已经采用了多进程架构——这直接塑造了 React 后来所有设计的底层约束。
当我们打开一个标签页,浏览器会启动一套复杂的进程模型:
| 进程 | 职责 |
|---|---|
| Browser 进程 | 地址栏、书签、前进后退、网络请求、下载管理 |
| Renderer 渲染进程 | 每个标签页一个,包含主线程、合成线程、光栅化线程 |
| GPU 进程 | 跨标签页的 GPU 任务,比如 CSS 动画、Canvas |
| Network 网络进程 | 独立处理 HTTP 请求,不阻塞 UI |
| Plugin 插件进程 | 第三方插件隔离运行 |
关键在于:每个标签页默认拥有独立的渲染进程。这意味着一个页面崩溃不会波及其他页面。
渲染进程内部:React 的"主战场"
渲染进程是 React 开发者的核心战场。它内部包含多个线程:
- 主线程(Main Thread):JavaScript 执行、DOM 操作、事件分发。这是 React 的
workLoop奔跑的地方。 - 合成线程(Compositor Thread):负责将图层合成并推送到屏幕。滚动和 CSS transform 动画可以在不经过主线程的情况下运行。
- 光栅化线程(Raster Thread):将绘制指令转化为像素。
React 的渲染是同步且在主线程上的。这意味着当 React 正在 reconcile(调和)虚拟 DOM 时,用户的点击事件、输入、动画都必须等待——除非 React 主动让出主线程。
为什么这影响了 React 的设计?
当你理解了这个架构,很多 React 的"奇怪设计"就有了答案:
为什么 React 18 引入 Concurrent Rendering? 因为主线程被 JavaScript 霸占时,用户的滚动、点击都是无响应的。React 需要一种机制,把大块的渲染工作切成小片,每片之间让浏览器喘口气。
为什么 requestIdleCallback 被 React Scheduler 重新实现?
因为浏览器原生的 requestIdleCallback API 在 Safari 上根本不存在,而且调度精度不够。React 必须自己造轮子。
为什么 useTransition 和 useDeferredValue 能"异步化"状态更新? 它们本质上是在告诉 React:"这个更新不急,可以在浏览器空闲时慢慢来,不要卡住用户的输入。"
一张图理解进程与线程
┌─────────────────────────────────────────┐
│ Browser 进程 │
│ (统筹协调,不参与页面内容渲染) │
└────────────────┬────────────────────────┘
│ 每个标签页独立运行
┌────────────────▼────────────────────────┐
│ Renderer 渲染进程 │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 主线程 Main Thread │ │
│ │ JS执行 / React workLoop │ │
│ │ DOM更新 / 事件处理 │ │
│ └─────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────▼───────────────────┐ │
│ │ 合成线程 Compositor │ │
│ │ 图层合成 / 推送帧到屏幕 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
本节小结
浏览器的多进程架构是 React 所有底层设计的起点。主线程只有一个,而 React 要在上面完成协调、计算、布局、绘制——这些需求之间的张力,催生了 Fiber、Scheduler、Concurrent Mode 这一整套体系。
理解了这个,你就理解了一个核心矛盾:React 想做的事,永远比它能用的时间多。