Skip to content

第十一章:Compiler 与 Foundation,React 把复杂度放到了哪里

从 2013 年到 Fiber、Hooks 和 Server Components,React 一直在做同一种交换:让应用作者写更声明式的代码,把更多调度、比较和集成责任交给基础设施。

这个交换到 2025–2026 年进入两个新层次:React Compiler 尝试自动决定哪些计算可以复用;React Foundation 则把项目资产和生态支持从 Meta 转入独立组织。一个处理代码复杂度,一个处理治理复杂度。

手工 Memoization 为什么越来越重

React 的粗粒度重算简单可靠,但大应用会产生大量无意义 render 和对象创建。长期以来,开发者通过以下工具建立 bailout:

  • class 的 shouldComponentUpdatePureComponent
  • React.memo 比较组件 props。
  • useMemo 缓存值。
  • useCallback 缓存函数身份。

这些 API 有共同问题:人必须判断依赖,维护引用稳定性,并理解缓存是否真的有收益。漏依赖会产生旧值,依赖过宽会失去缓存,过度 memoization 又增加代码和比较成本。

更根本的问题是,React 的语义目标本来只是“给定输入计算 UI”;为了配合运行时的对象身份优化,应用代码却逐渐充满缓存指令。若编译器能证明某段计算只依赖特定 reactive input,就可以自动生成更精确的 memoization。

Prepack:第一次编译器探索并没有直接成功

React 团队在 Compiler 1.0 发布回顾中把历史追溯到 2017 年的 Prepack。Prepack 试图在构建时部分执行 JavaScript,把已知计算预先求值,并生成等价优化代码。这是一项更通用、野心更大的 whole-program partial evaluation 探索。

项目最终停止,但团队获得两类经验:

  • 动态 JavaScript 的别名、可变性、异常和宿主行为使全局推理很难。
  • 如果框架能约束组件与 Hook 的写法,编译器可以在更窄的语义范围内做可靠分析。

Hooks 的纯 render、稳定调用顺序和 Rules of React 为后续编译器提供了更可分析的结构。这里不是“Hooks 只为 Compiler 发明”,而是运行时 API 与编译优化逐渐形成相互支持的约束。

2021 原型与重写

2021 年 React Conf 展示过一版自动 memoization 原型,后来没有直接成为正式产品。团队重写架构,把 Babel AST 降低到自己的 Control Flow Graph / High-Level Intermediate Representation。

当前 HIR.ts 顶部用一行概括 pipeline:

text
AST → lowering → HIR → analysis → Reactive Scopes → codegen → AST

使用自有 HIR 的原因,是 JSX/JavaScript AST 更接近语法,而优化需要回答控制流和数据流问题:一个值在哪些路径被定义?对象是否可能被修改?某条读取依赖哪些 props、state 或 Hook result?提前 return 后哪些计算仍可独立缓存?

Compiler 通过多轮分析推断可变性、别名与 reactive dependency,再划分 scope 并生成缓存代码。它当前作为 Babel plugin 发布,但内部表示尽量与 Babel 解耦,也在与 SWC、Oxc 等工具链探索集成。

Compiler 不会替代 Fiber

编译器优化组件函数,Fiber 仍负责:

  • 保存组件和 Hook 状态。
  • 处理 update queue 与 lanes。
  • 协调 children 和 key。
  • 捕获 Suspense/error。
  • 管理 render/commit phase。
  • 调用 renderer host config。

Compiler 减少某些值、函数和子树的重复计算,无法决定 DOM mutation、网络 boundary 或全局调度。它也不能把任意阻塞算法自动切成 Fiber units。

二者的关系更像:Compiler 提供更好的 bailout 信息,runtime 决定何时需要执行和提交。

Compiler 还承担规则验证

Compiler 1.0 不只生成 memoization,也把一部分分析通过 eslint-plugin-react-hooks 暴露为诊断。若代码违反 Rules of React、在 render 中不安全地修改值,编译器可以跳过优化并提示问题。

这种策略很关键:编译器若在无法证明安全时强行改写,可能改变语义。React 选择渐进采用、按组件 gating,并建议现有应用保留测试。Compiler 兼容 React 17 及以上,也说明它被设计成可独立 rollout 的工具,而不是 React 20 才能启用的断代功能。

Compiler 仍有代价:构建链更复杂,升级可能改变 memoization 粒度,隐藏违反规则的代码可能在优化变化后暴露问题。官方因此建议良好的端到端测试,并在覆盖不足时精确锁定版本。

本文判断:React Compiler 延续了 React 的经典策略——先让简单语义成立,再把性能责任逐步移入基础设施。但自动化越深入,应用对工具链正确性和诊断质量的依赖也越高。

Create React App 的退场:构建工具不再足够

2016 年 CRA 通过统一 Babel、Webpack、lint 与 dev server,解决“怎样开始一个 React 项目”。到 2025 年,React 团队宣布不再推荐用 CRA 创建新应用,原因不只是维护者不足,也包括纯 build tool 难以提供生产应用需要的 routing、data fetching、code splitting、SSR、streaming、mutation 和缓存整合。

React 官方开始推荐 framework;不适合 framework 的应用则使用 Vite、Parcel、Rsbuild 等工具自行搭建。这个变化看似与 core 无关,实际对应能力重心:

  • Suspense 需要数据与代码加载配合。
  • RSC 需要模块图和 server/client manifest。
  • Actions 需要服务器 endpoint 与路由。
  • Partial pre-rendering 需要构建、CDN 和请求时恢复配合。
  • Compiler 需要构建期 transform。

React 仍可作为 client library 单独使用,但官方“最佳路径”越来越依赖框架。早年 core 把应用架构留给生态,后期则通过协议和 feature 促使框架成为必要执行层。

19.2:时间、可见性与工具继续进入 Core

2025 年 10 月的 React 19.2 加入 <Activity>useEffectEventcacheSignal、Performance Tracks,以及 partial pre-rendering。

这些 feature 看似分散,却延续几条长期主线:

  • <Activity> 让隐藏子树可保留 state、卸载 effect,并以低优先级继续准备,是 Offscreen/Fiber 调度思想的产品化。
  • useEffectEvent 把 effect 中“需要读取最新值但不应触发重新同步”的事件逻辑单独建模,回应 Hooks 闭包与依赖数组的长期摩擦。
  • Performance Tracks 把 lane、render 与 effect 工作直接映射到浏览器性能面板,承认并发 runtime 需要更好的可观察性。
  • Partial pre-rendering 把静态 shell 与动态恢复拆开,继续扩展 server renderer 的时间边界。

React 不再只提供组件 API,也在提供调度语义、服务端生命周期和诊断工具。

从 Meta 项目到 React Foundation

2025 年 10 月,React 团队宣布计划建立独立 Foundation;2026 年 2 月 24 日,React Foundation 正式启动,由 Linux Foundation 托管。React、React Native 与 JSX 等项目不再归 Meta 所有。

Foundation 的董事会负责资金、基础设施、商标、会议和生态支持;技术方向则被明确设计为独立于董事会,由贡献者和维护者的技术治理结构决定。创始成员包括多家长期参与 React 生态的公司。

这项变化回应了一个已经存在多年的事实:React 的实现、renderer、framework、工具链与生产用户跨越许多公司,项目影响力早已超过单一赞助方。独立所有权有助于供应商中立和资源多元化,但治理结构仍需在实践中证明如何处理路线争议、维护者代表性与安全响应。

史料事实:Foundation 启动公告同时说明,仓库、网站和基础设施迁移仍是持续工作,技术治理也在由 provisional leadership council 继续完善。成立组织并不等于所有治理问题当日完成。

2026 年的 React 是什么

截至 2026 年,React 已很难只用“View library”概括。它至少同时包含:

  • element 与 component programming model。
  • Fiber reconciler 与 Scheduler。
  • DOM、Native、Fizz、Flight 等 renderer/protocol。
  • Hooks、Suspense、Transitions、Actions 等状态与时间语义。
  • RSC 的 server/client module boundary。
  • Compiler 与 lint diagnostics。
  • 与 framework、bundler、DevTools、浏览器性能面板的集成。

但早期核心仍清晰可见:组件根据输入返回描述,React 可以重新执行它;不可逆变化集中在受控提交点;宿主、时间和数据边界由运行时逐步接管。

React 把复杂度放到了哪里

React 经常让应用代码更短,却没有让系统总复杂度消失。它主要做了四次搬运:

  1. 从手工 DOM 更新搬到 reconciler。 开发者少写 transition 指令,runtime 维护身份与 diff。
  2. 从同步控制流搬到 Fiber/Scheduler。 应用少处理抢占,runtime 维护 lanes、重放与一致 commit。
  3. 从 client data plumbing 搬到 framework/Flight。 组件更靠近数据,构建和部署维护模块引用、缓存与协议。
  4. 从手工 memoization 搬到 Compiler。 组件更接近直接表达,编译器维护 reactive scope 与优化正确性。

每次搬运都让主路径更声明式,也扩大基础设施的可信计算基。React 的争议往往来自不同团队对这笔交易的偏好:有人愿意接受复杂 runtime 换统一模型,有人更偏好简单 runtime 与显式应用控制。

仍未结束的问题

React 的下一阶段仍有若干开放问题:

  • Compiler 能否在复杂动态 JavaScript 中保持可预测产物和低构建成本?
  • Server Components 的跨 framework 标准化程度能否提高,而不牺牲优化空间?
  • Server Functions 的安全、缓存与部署语义如何变得更容易审计?
  • Activity、View Transitions 与资源加载能否形成一致的 navigation model?
  • React Native、Web 与其他 renderer 的 feature 差距怎样治理?
  • Foundation 的独立技术治理如何平衡维护者、企业与社区?

一部 Chronicle 不应把“现在”写成结局。React 的历史更像一系列延期兑现的架构选择:2013 年的轻量描述打开多 renderer;2016 年的 Fiber 原型直到 2022 年才以 concurrent features 大规模进入产品;2018 年的 Hooks 在 2025 年成为 Compiler 可分析的基础;2020 年的 RSC 研究在 2024 年稳定,又在 2025–2026 年暴露新的安全责任。

本文结论:React 最持久的能力不是某个 API,而是反复重写“如何执行组件”,同时尽量不让组件作者重写“组件表达什么”。这也是理解它所有技术选择的最好入口。

从今天回到初始提交,render 旁那句“不能假设何时或调用多少次,因此不得有副作用”仍像整部历史的压缩版本。Fiber、Hooks、Concurrent React、RSC 与 Compiler,只是在越来越大的系统中认真兑现这句话。

Engineering history, reconstructed from primary sources.