React Compiler

共 31 题
📑 题目列表 31 题
#
★★★

1. React Compiler 在事件处理器内联与闭包捕获的边界条件

React Compiler 如何处理事件处理器中的内联与闭包捕获?其边界条件是什么?

  • 事件处理器不参与渲染依赖分析
  • 闭包捕获与缓存失效的边界
  • 违反规则时的 bailout 行为

React Compiler 分析的是"渲染期间的响应式依赖",事件处理器(onClick 等回调)不在渲染路径上:它们的执行不触发渲染,因此 Compiler 不会为事件处理器内部的读取建立"渲染依赖",也不会对它们做记忆化——这符合 React 语义(事件回调每次渲染重新创建是安全的,缓存反而可能引入过期值)。事件处理器内联闭包捕获的边界:若回调在渲染中创建并被传入子组件,Compiler 会分析其"读取的响应式值"并缓存回调(等价 useCallback,依赖变化才重建),保证引用稳定且值不过期;但若回调读取了"渲染期之外的可变值"(模块级变量、ref.current 之外的非常规读取),Compiler 无法证明其响应性,会谨慎处理或 bailout。

边界条件:Compiler 只信任"符合 Rules of React"的读取(props/state/context 及派生值);事件处理器中读写"不受控可变对象"(如 this 之外的全局单例)会触发 bailout(该组件不记忆化);"捕获最新值"的意图(回调里读 state)Compiler 通过依赖分析自动保证(依赖变化则重建回调),开发者无需 useCallback 包裹——这正是"自动记忆化"对闭包陷阱的消除;若代码在事件处理器中调用"渲染期创建的、读取响应式值的函数",Compiler 也会对其依赖建模。遇到无法分析的模式,Compiler 会跳过优化并给出诊断(不改变行为),开发者可用 'use memo'/'use no memo' 控制。

答题先讲"事件处理器不在渲染依赖路径"的语义基础,再讲回调缓存(依赖变化重建)与"读取非常规可变值触发 bailout"的边界,最后落到 Compiler 对闭包陷阱的自动消除。

#
★★★

2. React Compiler 在 ref/props 不可序列化的处理与编译失败的 fallback 策略

React Compiler 如何处理 ref 与不可序列化 props?编译失败时的 fallback 策略是什么?

  • Compiler 对 ref 读取的响应性建模
  • 不可序列化 props 与编译器无关的边界
  • bailout 与降级策略

React Compiler 对 ref 的处理:把"渲染期间读取 ref.current"视为对可变对象的读取,若代码满足"写入仅在事件/effect 中、读取仅在渲染初始化"的可控模式,Compiler 会将其建模为稳定值(不触发记忆化失效);若 ref 在渲染中与响应式值混合使用或读写模式不可控,Compiler 会保守处理(当作可能变化,禁用相关缓存)。不可序列化的 props(函数、class 实例)是"运行时序列化"概念,Compiler 是编译期工具,不校验序列化——但它会分析"传递引用稳定性":不可序列化对象若被传入子组件,Compiler 通过记忆化保证其引用在依赖未变时稳定(减少子组件重渲染),这间接缓解了"每次渲染新对象"的问题。

编译失败/无法优化的 fallback 策略:Compiler 逐组件独立分析,无法证明符合 Rules of React 的组件会 bailout——跳过记忆化、保持原语义执行(不报错、不改变行为),并在 build 输出/CLI 中给出诊断(哪些文件、哪些原因);bailout 组件内部使用的手工 memo/useMemo 仍然有效;策略上:优先修复可修复的违规(把渲染期副作用移到 effect、把可变读取改为受控模式),对不可修复的(第三方模式、动态注入)用 'use no memo' 显式退出或用 'use memo' 断言强制;增量启用:按包设置 compilationMode,先收集成功率与 bailout 原因再全量。

答题先讲 ref 的可控读写建模与不可序列化 props 的"引用稳定化"间接收益,再重点讲 bailout fallback(跳过优化不改变行为 + 诊断)与 'use memo'/'use no memo' 控制手段。

#
★★★

3. Compiler 在 React Native 与 SSR 场景的差异与限制

React Compiler 在 React Native 与 SSR 场景有什么差异与限制?

  • RN 端的手工记忆化替代
  • SSR 端与流式渲染的协作
  • 平台相关的边界

React Compiler 是平台无关的(它编译 JS 组件,不关心渲染目标),但两端有差异:React Native——RN 没有浏览器 DOM,组件用原生视图,Compiler 的自动记忆化同样生效(组件跳过重渲染、值缓存),替代 RN 生态中手工 memo/useMemo 的常见实践;RN 的 Hermes 引擎执行编译产物,Compiler 产物的兼容性由 Babel 插件(babel-plugin-react-compiler)保证;RN 场景中"渲染期读取原生模块/设备 API"的模式需符合 Rules of React,否则 bailout;SSR——服务端渲染中 Compiler 的记忆化语义不变(缓存推导与客户端一致),但服务端场景"每次请求可能不同 props",Compiler 的依赖分析仍按 props 变化生效;流式 SSR 中组件可能被多次执行(Suspense 边界恢复),记忆化保证同请求内重复渲染的一致性,减少重复计算。

限制:Compiler 不处理"平台特有"的运行时问题(RN 的同步布局、SSR 的序列化约束仍需开发者处理);SSR 场景的缓存要避免"跨请求共享可变状态"(Compiler 的记忆化是渲染级,不跨请求);两端都要保持"渲染纯函数"契约,违反规则的代码(如渲染期读 AsyncStorage、直接操作原生 DOM 的浏览器 API)都会被 bailout;React 19 中 RN 与 SSR 均为 React 官方的支持场景,Compiler 的 rules(Rules of React)对两端一致。

答题先点明 Compiler 平台无关的本质,再分别讲 RN(替代手工 memo、Hermes 兼容)与 SSR(记忆化语义一致、流式重执行一致性、不跨请求共享)的差异,最后给"渲染纯函数"的统一限制。

#
★★★

4. Compiler 与并发特性的协同

React Compiler 与并发特性(Transition、Suspense、use())如何协同?协同的边界是什么?

  • 自动记忆化对并发渲染的影响
  • 与 Transition/Suspense 的协作
  • 缓存推导与可中断渲染的一致性

Compiler 与并发特性协同的要点是"记忆化不改变并发语义":Compiler 生成的缓存只是"依赖未变时跳过重渲染/重计算",渲染仍可被中断、恢复、丢弃——缓存推导基于"渲染纯函数"假设,与并发渲染的确定性要求一致(并发渲染要求组件可重复执行,Compiler 保证重复执行中缓存行为稳定)。与 Transition 的协同:过渡更新中组件重渲染,Compiler 的记忆化减少过渡期间的重计算量(低优先级任务更快完成),且不干扰 Transition 的可打断性;与 Suspense 的协同:挂起恢复后组件重新执行,Compiler 缓存使恢复渲染复用先前计算结果(依赖未变),加速恢复;与 use() 的协同:use(Promise) 读取的资源是响应式依赖,Compiler 会纳入依赖分析(Promise 引用变化触发重渲染/重计算)。

边界:Compiler 只优化"可证明的纯渲染";并发下的"渲染期读写 ref/外部可变值"既是并发 bug 源也是 Compiler bailout 源——修复这类代码同时受益于并发正确性与自动记忆化;Transition 内"延迟值"(useDeferredValue)的依赖是响应式的,Compiler 正常记忆化,但"deferred 值与当前值不一致"的 UI 语义需开发者保证;Suspense fallback 与错误边界的渲染也走同一记忆化逻辑。工程上:Compiler 开启后并发特性的使用方式不变(无需为 Compiler 调整),收益叠加(并发保证响应性、记忆化减少工作量)。

答题核心是"记忆化不改变并发语义、只减少重复计算",再分别讲 Transition(重计算减少)、Suspense(恢复复用缓存)、use()(纳入依赖分析)的协同,最后给"渲染纯度是两者共同前提"的边界。

#
★★★

5. React Compiler 的 "React Forget" 在重渲染优化的现代实践

React Compiler 的前身 "React Forget" 是什么?它在重渲染优化上的现代实践是什么?

  • Forget 项目的由来与定位
  • 自动记忆化对重渲染的治理
  • 现代实践与工具链

"React Forget" 是 React 团队对"自动记忆化编译器"的早期代号(约 2021 年提出):愿景是让开发者"不再手工写 memo/useMemo/useCallback",编译器自动推导组件的响应式依赖并生成记忆化代码,解决"重渲染优化靠人肉维护、容易漏或错"的痛点。它最终演化为 React Compiler(2024 年开源、1.0 GA),原理一致:编译期分析 → 生成等价 useMemo/useCallback/React.memo 的产物,且能跨组件传播"引用稳定"(被记忆化的组件输出稳定,上游自动受益)。

现代实践:以 Babel 插件(babel-plugin-react-compiler)接入构建(Vite/Next.js/RN),或 eslint-plugin-react-compiler 先行检查代码可编译性;实践路径——先小范围(按包 compilationMode)验证编译成功率与行为回归,配合性能门禁(对比开启前后渲染耗时、组件数)再逐步扩大;'use memo'/'use no memo' 指令管理边界(强制或退出优化);处理 bailout 报告(哪些组件因违反 Rules of React 未优化,修复后获得优化);与 Profiler 配合验证收益;"React Forget 精神"的现代落地:日常不再手工记忆化,把精力放在数据流与组件结构设计上,性能收益由编译器保证且可回归。

答题先讲 Forget 的愿景(自动记忆化、消灭手工 memo)与 Compiler 的演进关系,再讲现代实践路径(插件接入、按包启用、bailout 治理、性能门禁),点出"手工记忆化退居兜底"的实践结论。

#
★★★

6. React Compiler 在 Vite 与 Next.js 的集成路径与构建配置

React Compiler 在 Vite 与 Next.js 中如何集成?构建配置有哪些要点?

  • Vite 的 babel/react 插件集成
  • Next.js 的配置项启用
  • 构建配置与版本要求

Vite 集成:Vite 使用 esbuild 转译,React Compiler 通过 babel-plugin-react-compiler 接入——配置 vite-plugin-react 的 babel 选项(或在 vite.config 的 esbuild 之外挂 babel 插件,较新方式是用 @vitejs/plugin-react 的 babel: { plugins: [['babel-plugin-react-compiler', { target: '19' }]] }),并安装 react-compiler-runtime 运行时依赖;注意 esbuild 与 babel 双转译链路的顺序(Compiler 应在 JSX 转换之前/之后按官方要求配置)。Next.js 集成:Next.js 15.3+(及 16)内置支持,在 next.config 中配置 experimental.reactCompiler = true(或 stable 配置项),生产与开发构建均启用,支持 App Router 与 Pages Router,与 Turbopack 兼容;也可通过 SWC 原生支持(Next 的 SWC 插件路径)。

配置要点:确认 React 版本(Compiler 1.0 GA 支持 React 17-19,target 配置 19 以获得新运行时);开发环境开启编译错误提示(Next.js 会在编译期报 Rules of React 违规,帮助修复);按包启用 compilationMode('annotation'/'all' 等)控制范围;bailout 报告通过构建日志或 lint 插件收集;CI 中加"编译成功率"门禁;SSR/流式场景无需额外配置(Compiler 产物两端一致);运行时包 react-compiler-runtime 需随应用打包(体积极小)。常见坑:自定义 Babel 配置冲突、monorepo 下根 babel 配置未生效、Compiler 与代码分割/lazy 的正常协作(不冲突)。

答题按"Vite(babel 插件 + vite-plugin-react 配置)与 Next.js(内置配置项 + SWC/Turbopack)"两条集成路径展开,再讲版本、范围、bailout 报告与 CI 门禁的配置要点。

#
★★★

7. React Compiler 编译产物的 reactive dependencies 分析在 useEffect/useMemo 自动补全

React Compiler 如何通过 reactive dependencies 分析自动补全 useEffect/useMemo 的依赖?边界是什么?

  • 响应式依赖(reactive dependencies)的定义
  • 依赖自动补全与压缩
  • 与手动依赖数组的边界

Reactive dependencies 是 Compiler 分析的核心概念:指"渲染期间读取的、可能变化的响应式值"(props、state、context 派生值),Compiler 为每个组件与表达式建立"读取依赖"关系图。对 useEffect/useMemo,Compiler 会推导其回调实际读取的 reactive dependencies 并自动补全依赖数组——开发者可省略或压缩依赖(React 19 的 eslint 规则配合建议移除冗余依赖),编译器保证"闭包读取的值都在依赖里",从根源消除 exhaustive-deps 漏写导致的 stale closure;对 useMemo 自动生成缓存(依赖未变跳过计算)、对组件自动生成 memo 逻辑。

边界:Compiler 只补全"它可证明的响应式读取"——读取非响应式外部值(模块变量、可变单例、无法静态分析的动态访问)不会被建模,依赖不完整时 Compiler 选择 bailout(不自动补全)而非猜测;手动标注的依赖数组若与推导冲突(如故意只依赖部分值、依赖"恒定"值实现"只跑一次"),Compiler 尊重显式意图或提示移除;'use memo'/'use no memo' 可控制组件级行为;ref 的"读取即非响应式"语义(ref.current 变化不触发渲染)使 ref 不进入依赖(正确);Server Components 与客户端组件分析一致。工程实践:开启 Compiler 后把 exhaustive-deps 的"补全建议"切换为"移除冗余建议",手动依赖主要用于 bailout 或特殊意图场景。

答题先定义 reactive dependencies 与依赖图,再讲对 useEffect/useMemo 的自动补全与 stale closure 消除,最后给"不可证明读取触发 bailout、ref 不入依赖、显式意图被尊重"的边界。

#
★★★

8. Compiler 与 useMemo/React.memo 的协作与替代

React Compiler 与 useMemo/React.memo 如何协作?Compiler 是否完全替代它们?

  • Compiler 自动生成等价缓存
  • 手工 API 在 Compiler 下仍有效
  • 替代的边界(bailout、第三方)

React Compiler 自动生成与 useMemo/React.memo/useCallback 等价的记忆化逻辑:值缓存(useMemo)、组件级跳过重渲染(memo)、函数引用稳定(useCallback),其语义与手工 API 完全一致(依赖变化才失效),因此"日常手工记忆化被替代"。协作方式:已有的手工 useMemo/memo 代码在 Compiler 下继续有效(Compiler 识别并尊重显式缓存,不会删除或与其冲突);React 19 的 lint 建议在 Compiler 开启后移除冗余的依赖标注(Compiler 自动补全);开发者把精力从"哪里该 memo"转向"数据结构与组件拆分"。

替代的边界:Compiler 无法证明安全的组件(违反 Rules of React、不可编译的第三方交互)会 bailout——这些组件的性能只能靠手工 memo 或重构;第三方组件库代码不在编译范围,可手工 React.memo 包裹(Compiler 对"调用方"仍会记忆化 props 传递);"跨边界引用稳定"的显式契约(把稳定函数传给非 React 系统)可保留手工 useCallback;用 Profiler 验证 Compiler 未覆盖的热点并手工兜底。结论:Compiler 替代"日常优化劳动",手工 API 退化为"bailout 兜底 + 架构工具",两者协作而非互斥。

答题主线是"Compiler 自动生成等价缓存、替代日常手工优化",再讲手工 API 在 Compiler 下仍有效且不冲突,最后明确 bailout 与第三方场景仍需手工的边界。

#
★★★

9. React Compiler 1.0(GA)

React Compiler 1.0(GA)的发布意味着什么?它的能力边界与生产可用性如何?

  • 1.0 的能力范围与生产承诺
  • 兼容性与运行时要求
  • 生产启用与回滚策略

React Compiler 1.0(GA,2024 年底)标志着自动记忆化从实验走向生产就绪:编译规则(Rules of React 的静态分析)、记忆化生成(等价 useMemo/useCallback/memo)、bailout 诊断、'use memo'/'use no memo' 指令等全部稳定;官方承诺与 React 17-19 兼容(target 配置),运行时仅需极小的 react-compiler-runtime 包;与主流工具链(Babel 插件、Next.js 内置、Vite、RN)均有官方集成路径;DevTools/ESLint 配套(eslint-plugin-react-compiler)提供编译前检查。1.0 的意义:团队可以把它当作"默认开启的优化层"而非实验特性。

能力边界与生产实践:Compiler 优化"符合规则的组件",不保证 100% 覆盖(bailout 组件保持原行为,不破坏功能);生产启用建议——先在开发与 staging 用 compilationMode 按包灰度,收集编译成功率与运行时回归(重点:渲染期副作用、ref 滥用类代码在启用后是否出现行为差异),配性能门禁(对比渲染耗时/组件数)与快速回滚(构建开关一键关);1.0 的运行时在 React 19 下最优(新缓存 API),React 17/18 走兼容路径;SSR/流式/RN 均支持。风险点:非标准模式(动态成员访问、Proxy 包装、元编程)可能 bailout 或需重构,依赖第三方库行为的组件需回归验证。

答题先讲 1.0 的稳定内容(规则、生成、指令、工具链、兼容承诺),再讲生产启用路径(按包灰度、回归、门禁、回滚)与能力边界(bailout 不破坏功能),体现生产化视角。

#
★★★

10. React Compiler 与手工 useMemo/memo/useCallback 协同的取舍与边界条件

React Compiler 与手工 useMemo/memo/useCallback 协同的取舍是什么?边界条件有哪些?

  • 自动与手工缓存的双轨语义
  • 边界条件(bailout、第三方、契约)
  • 取舍原则(编译器优先)

协同的取舍原则是"Compiler 优先、手工兜底":Compiler 可优化的组件不写手工记忆化(冗余且增加维护面,lint 会提示移除);手工记忆化保留在三个边界——Compiler bailout 的组件(违反 Rules of React、无法静态分析的模式)、不可编译的第三方组件(在调用方手工 memo 或包一层)、需要"显式缓存契约"的场景(把稳定引用传给非 React 系统、跨模块共享的稳定值)。双轨语义:手工与自动生成的缓存都是"依赖未变即复用",二者并存不冲突(Compiler 尊重已有 memo,不会双重失效)。

边界条件细节:Compiler 的依赖分析含"函数的函数"(回调的读取也建模),手工 useCallback 若依赖不完整反而掩盖问题(Compiler 会按其分析继续),最佳实践是依赖交给编译器;'use memo'(断言可编译并强制记忆化)与 'use no memo'(退出)用于边界管控;bailout 报告是取舍依据——先看报告再决定"修代码使其可编译"还是"保留手工优化";性能关键路径用 Profiler 验证两端差异。取舍的本质:把"记忆化的正确性"从人脑责任转移给编译器,人工只处理编译器够不到的地方。

答题核心是"Compiler 优先、手工兜底"的取舍原则,再列出三个保留手工优化的边界(bailout、第三方、显式契约),最后讲双轨语义与 'use memo'/'use no memo' 管控。

#
★★★

11. Compiler 的"运行时优化"(Runtime Memoization)

React Compiler 的"运行时优化"(Runtime Memoization)是什么?它与编译期分析如何配合?

  • 编译期依赖分析 + 运行时缓存指令
  • React 19 的新缓存运行时(useMemoCache)
  • 运行时优化的语义与收益

React Compiler 采用"编译期分析 + 运行时缓存"的架构:编译期(Babel/ESLint)完成响应式依赖分析与记忆化方案推导(哪些值缓存、哪些组件跳过重渲染),生成调用"缓存运行时"的产物指令(React 19 中如 useMemoCache:按槽位缓存值,依赖变化才重算);运行时优化指"缓存指令在 React 运行时的执行"——它是极薄的一层(react-compiler-runtime),只负责按编译期确定的槽位存取缓存,不参与任何决策。这样既获得了编译期的精确分析,又保持了运行时最小开销(无 babel 运行时、无额外调度)。

语义与收益:缓存的语义严格等价手工 useMemo(依赖未变跳过计算),组件级跳过重渲染等价 memo;收益——渲染期重复计算消除、引用稳定传播(被缓存组件输出稳定,上游记忆化自动成立,形成"记忆化链")、与并发特性兼容(缓存是渲染局部的,可中断渲染安全);React 17/18 走兼容的运行时路径(无新 API 时用旧机制实现等价语义)。运行时优化的边界:缓存按"渲染"生命周期管理(不跨渲染持久),组件卸载即释放;bailout 组件无缓存指令(等价未优化);运行时包极小(约 1KB),不构成包体负担。

答题先讲"编译期分析 + 运行时缓存指令"的双层架构与 useMemoCache 槽位机制,再讲缓存语义等价性与引用稳定传播的收益,最后给渲染级生命周期与 bailout 边界。

#
★★★

12. React Profiler 与 DevTools 性能分析

React Profiler 与 DevTools 如何做性能分析?有哪些关键指标与分析流程?

  • Profiler 的火焰图与提交耗时
  • 关键指标(render 时长、组件耗时)
  • 分析流程与优化验证

React DevTools 的 Profiler 记录每个 commit 的渲染信息:时间线视图展示提交序列(含调度事件、Suspense、Transition、Performance Tracks),火焰图展示每个组件在本 commit 的渲染耗时(self 与 total),交互图把"点击/输入"关联到其引发的更新。关键指标:commit 总耗时(渲染 + commit 阶段)、单组件 self 耗时(定位重渲染热点)、渲染次数(找出"没变却重渲染"的组件,即 memo 失效或 context 广播)、每次提交的触发源(哪个 setState/事件)。

分析流程:先在交互图找到"卡顿操作"对应的 commit → 火焰图定位高耗时/高频组件 → 判断原因(props 新引用导致 memo 失效、context 值变化广播、大列表整树渲染、未用并发特性让出主线程)→ 针对性修复(稳定引用、拆分组件、useDeferredValue/Transition、虚拟列表、Compiler)→ 用 Profiler 复测对比(验证修复生效、无新回归)。注意事项:Profiler 有记录开销(开发环境使用,或 profiling 构建)、StrictMode 下双渲染不影响分析、结合浏览器 Performance 面板看主线程长任务与绘制、Performance Tracks 辅助看调度细节;React 19 的 profiling bundle 提供生产环境分析能力。

答题按"记录内容(时间线/火焰图/交互)→ 关键指标(耗时、次数、触发源)→ 分析流程(定位-归因-修复-复测)"组织,补充记录开销与工具配合的注意点。

#
★★★

13. Compiler 对依赖数组推断的边界

React Compiler 对依赖数组推断的边界是什么?哪些代码会导致推断失败?

  • 推断的适用范围(可静态分析的读取)
  • 推断失败的模式(动态访问、外部可变)
  • 失败后的行为与治理

Compiler 的依赖推断基于静态分析:能处理"直接读取 props/state/context、明确的数据流(函数调用链、数组/对象字段访问)",推断出组件与回调的响应式依赖;推断失败的边界包括——动态成员访问(obj[key] 其中 key 来自外部,无法确定读取路径)、外部可变状态(模块级变量、全局单例、第三方 store 的非受控读取)、元编程模式(Proxy、Reflect 动态调用)、渲染期调用"不可分析的函数"(来自未编译模块、动态 import)、以及违反渲染纯度的代码(渲染期写外部变量)。这些模式编译器无法证明"依赖是什么",因此不推断、不自动补全,直接 bailout 该组件。

失败后的行为:组件保持"无自动记忆化"的原始语义(功能不受影响),构建输出/CLI 报告 bailout 原因(文件名 + 违规模式),eslint-plugin-react-compiler 在开发期提前提示;治理:能修则修(把动态访问改为显式数据流、把外部可变状态改为 useSyncExternalStore 受控读取、把渲染期副作用移到 effect/事件),不能修则接受 bailout 或用 'use no memo' 显式退出;对 bailout 组件仍可手工 memo/useMemo 兜底;治理目标是把 bailout 率压到可接受区间(如 <5%),高 bailout 率说明代码库存在系统性模式问题需重构。

答题先讲推断的适用范围,再列四类推断失败模式(动态访问、外部可变、元编程、不可分析调用),最后讲 bailout 行为、修复治理与 bailout 率指标。

#
★★★

14. React Compiler 的自动 memoization 在性能预算与首屏渲染的工程价值

React Compiler 的自动 memoization 在性能预算与首屏渲染中有什么工程价值?

  • 自动记忆化对渲染开销的治理
  • 首屏渲染中的收益与边界
  • 性能预算的度量与承诺

自动 memoization 的工程价值在于"消除无谓重渲染的计算开销":依赖未变的组件跳过重渲染、值缓存跳过重计算、引用稳定抑制子组件重渲染,把"重复劳动"从渲染预算中剔除,让有限的预算(主线程时间)花在真正变化的部分。对首屏渲染:收益有两面——一是减少"水合/挂载后初始渲染"的重复计算(首屏的重复计算消除,缩短 TTI);二是"父组件更新引发的整树重渲染"被抑制,间接减少后续交互时的长任务;边界:Compiler 不减少"首次渲染本身的必要工作量"(组件首次渲染无法跳过)与"数据获取、DOM 布局"开销,首屏 LCP 主要由数据与资源策略决定,memoization 是辅助而非决定因素。

性能预算实践:把 Compiler 纳入"渲染预算门禁"——用 Profiler/Performance Tracks 度量开启前后"平均提交耗时、p95 提交耗时、重渲染组件数",建立回归基线(CI 对比),超限即回滚;把"重渲染率"作为团队质量指标(Compiler 覆盖后应显著下降);配合并发特性:Transition 内的工作量因记忆化减少,时间切片更快完成。工程承诺:开启 Compiler 后团队不再需要"手工 memo 维护"就能持续享受重渲染优化,预算管理从"优化劳动"转向"数据流与组件架构"。

答题先讲自动记忆化对重复计算的消除与首屏的两面收益(重计算消除 vs 首次渲染不减),再讲预算度量(提交耗时、重渲染率、CI 门禁)与并发配合,构成预算视角的完整答案。

#
★★

15. React 19 的 在路由切换的视觉过渡工程实践

在路由切换的视觉过渡中有哪些工程实践?如何编排过渡与处理可访问性?

  • 路由切换的声明式过渡
  • 过渡编排(共享元素、命名视图)
  • prefers-reduced-motion 与降级

路由切换场景中,用 包裹"随路由变化的内容"(通常配合 key=route 强制替换),React 更新被包进 startViewTransition,浏览器对旧页面与新页面截取快照并播放默认淡入淡出;工程实践要点:命名视图(::view-transition-name)让新旧快照做"共享元素过渡"(如列表项放大为详情页头部),需要给元素设 view-transition-name 并在新页面对应元素复用同名;整页切换时根快照缩放动画可裁剪(设置 transform-origin 或直接移除);过渡时长用 CSS 变量控制(--view-transition-duration 或在 ::view-transition-group 设置),避免默认动画过慢。

可访问性与降级:prefers-reduced-motion: reduce 时关闭动画(媒体查询下跳过或使用极短过渡),尊重用户减少动效偏好;过渡中焦点管理照常(路由切换后焦点应移到新页面标题);不支持 View Transitions 的浏览器自动降级为直接更新(React 内部特性检测),无需额外分支;避免在过渡回调中做高开销同步工作(会拖慢快照截取);多边界过渡(路由 + 模态同时开)需规划命名与层级,防止快照互相遮挡。实践建议:把过渡封装为路由层组件(LayoutRoute 包裹),统一管理命名、媒体查询与降级。

答题按"声明式包裹 + 命名视图共享元素过渡 → 时长/动画编排 → reduced-motion 与降级"展开,再提焦点管理与多边界编排,覆盖工程实践全貌。

#
★★

16. React Compiler 对现有代码库的迁移成本与兼容性边界

React Compiler 对现有代码库的迁移成本是什么?兼容性边界在哪里?

  • 迁移步骤与增量启用
  • 行为回归的高风险模式
  • 兼容性边界(版本、模式)

迁移成本分为三部分:工具接入成本(安装 babel-plugin-react-compiler / eslint 插件、配置 compilationMode、添加 react-compiler-runtime 依赖,通常数小时级);代码治理成本(修复 lint 插件报告的 Rules of React 违规——渲染期副作用、外部可变读取、动态模式,存量库中这类代码越多治理成本越高,是主要成本项);验证成本(行为回归测试,重点覆盖 ref 滥用、渲染期副作用、与第三方库交互的组件)。增量策略:compilationMode 按目录/包启用,先低风险模块后全量;每步收集"编译成功率 + bailout 原因 + 行为回归",配性能门禁。

兼容性边界:React 17/18 兼容(旧运行时路径)、19 最优;Class 组件不支持编译优化(保持原行为,需手工 memo);未编译的第三方代码不受影响(调用方仍被记忆化);SSR/RN 均支持;不兼容的高风险模式——渲染期写模块变量(启用后可能行为差异)、依赖"渲染次数"的逻辑(记忆化减少执行次数,若代码依赖执行次数会变化)、依赖闭包"每次重建"的第三方库(如某些测量库依赖引用变化触发回调,需手工兜底);'use no memo' 提供逃逸门。总体:迁移是"接入易、治理中、验证必须",收益随覆盖率增长。

答题按"接入、治理、验证"三部分拆解迁移成本,再讲增量启用策略与高风险模式(渲染期副作用、执行次数依赖、第三方引用依赖)的兼容边界。

#
★★

17. React Compiler 编译产物的可读性与调试的工程取舍

React Compiler 编译产物的可读性如何?调试时如何取舍?

  • 编译产物的形态与可读性
  • 调试手段(source map、bailout 报告)
  • 可读性与性能的权衡

React Compiler 的编译产物是在源码基础上插入"缓存指令"(如 useMemoCache 调用、条件跳过逻辑)与记忆化包装,保持了与原代码近似的结构(不像 minify 那样混淆):变量名保留、代码顺序基本不变,主要增加的是缓存槽位读取/写入与分支判断,因此"结构可读、细节需理解"——能看懂"这是缓存",但逐行对照原逻辑需要一点对缓存模式的认识。官方设计上优先"行为等价 + 可调试",产物可读性优于手工抽象过度的写法;source map 默认可用,DevTools 断点定位到源码(缓存指令不影响断点语义)。

调试取舍:行为问题优先在"编译前源码"层调试(lint 插件 + DevTools),Compiler 产物只在"怀疑编译 bug"时检查(可用 babel 输出对比);性能问题用 Profiler(产物层面度量渲染耗时);bailout 报告与 'use memo'/'use no memo' 指令用于"该优化没生效"的排查;生产构建可额外压缩(产物再经 minifier 处理,此时可读性让位于体积)。工程取舍原则:日常不读产物(它应作为"黑盒优化层")、调试靠 source map 与工具链、产物可读性只影响极少数"编译器行为核查"场景——若团队频繁需要读产物,说明缓存模式心智不熟或工具链配置有问题。

答题先讲产物形态(源码近似 + 缓存指令)与可读性定位,再讲调试手段(source map、Profiler、bailout 报告)与"黑盒优化层"的取舍原则。

#
★★

18. React 19 的 Concurrent Rendering 在长列表与高交互的工程应用

React 19 的 Concurrent Rendering 在长列表与高交互场景如何应用?工程实践与边界是什么?

  • 并发渲染对长列表的调度收益
  • 高交互场景的优先级设计
  • 与虚拟列表等方案的组合

长列表场景中,Concurrent Rendering 的价值是"渲染不阻塞输入":把列表的过滤/更新包进 startTransition 或使用 useDeferredValue,渲染按时间切片断续进行,期间输入事件随时抢占主线程;配合 Suspense 可在等待新数据时保留旧列表(过渡期不闪 fallback)。高交互场景(拖拽、画布、富文本)中,把"交互直接关联的更新"保持紧急(默认 setState),把"派生渲染"(面板刷新、统计重算)降级到 Transition,保证跟手性;React 19 的自动批处理让同一事件内的多次 setState 合并渲染,减少提交次数。

工程实践与边界:并发渲染不缩小"渲染总量"——上万行列表的整树渲染即使切片也耗时,需叠加虚拟列表(只渲染可视区,react-window/react-virtuoso)与 useMemo 缓存(依赖 deferred 值)从规模上治理;Transition 内的大规模 DOM 更新可能造成"中间态可见性"问题(分批渲染造成内容跳变),对"必须整块替换"的 UI 用紧急更新或谨慎使用;高交互场景避免在渲染路径做高开销计算(优先 useMemo/Compiler);flushSync 仅用于必须同步读 DOM 的场景;性能度量用 Performance Tracks 对比开启前后的长任务分布,验证"输入响应(INP)改善"而非只看总耗时。并发渲染是"调度层优化",与"规模层优化"(虚拟化)组合才是完整方案。

答题主线是"并发渲染解决阻塞、不解决规模",先讲长列表/高交互的优先级设计与保留旧 UI,再明确虚拟列表与缓存才是规模治理手段,最后给度量方法。

#
★★

19. React Compiler 对第三方组件库(不能编译)的边界与降级策略

React Compiler 对第三方组件库(不在编译范围)的边界是什么?降级策略有哪些?

  • 第三方代码不编译的边界
  • 调用方记忆化与手工 memo 兜底
  • 降级与监控策略

React Compiler 只编译"纳入构建范围"的代码(通常是应用源码),node_modules 中的第三方组件库默认不编译:其内部没有自动记忆化,行为与引入前一致(不因 Compiler 出现行为差异)。边界影响:第三方组件每次渲染仍会执行(除非其自身有 memo);调用方传入的 props 引用稳定性由 Compiler 在"应用侧"保证——给第三方组件传的 props 会被自动记忆化(依赖未变引用不变),间接减少其重渲染;若第三方组件自身"内部状态频繁自更新"或"渲染开销大",应用侧无法优化其内部,只能靠外部手段。

降级策略:对已知性能热点的第三方组件,应用侧手工 React.memo 包裹(浅比较 props,抑制无谓重渲染);用 Profiler 定位"第三方组件重渲染占比"决定是否值得处理;性能敏感的组件优先选"自身带 memo/高效实现"的库(如虚拟列表库、图标库);第三方库若依赖"渲染期副作用"等非标准模式,Compiler 无法证明调用安全时会 bailout 应用侧组件(以库的用法为准);监控:构建时收集"bailout 涉及第三方调用"的报告,运行时用 Profiler 追踪第三方组件耗时变化,作为升级/替换库的决策依据。原则:第三方库是"黑盒",应用侧的引用稳定与 memo 包裹是唯一可控杠杆。

答题先明确"第三方不编译"的边界与调用方记忆化的间接收益,再讲手工 memo 包裹、库选型与 Profiler 追踪的降级策略,最后落到"黑盒 + 应用侧杠杆"的原则。

#
★★

20. React 19 的 Suspense Transition 在数据获取的工程实践

Suspense 与 Transition 组合(Suspense Transition)在数据获取中的工程实践是什么?如何避免 fallback 闪烁?

  • Transition 中挂起保留旧 UI 的机制
  • 数据获取的分层(缓存、预取)
  • 避免闪烁与流式协作

Suspense 与 Transition 的组合解决"数据更新时的 UI 闪烁":普通更新中组件挂起会立刻显示 fallback(旧内容消失);把更新包进 startTransition 后,挂起期间 React 保留旧 UI(旧数据继续显示),新数据就绪后才切换,用户感知为"无缝刷新"。工程实践:切换筛选条件/翻页/切换详情时,用 startTransition 包裹"更新数据状态的 setState",配合 Suspense 边界的 fallback 只用于"首次加载"(无旧内容可保留时);useTransition 的 isPending 用于显示轻量"刷新中"指示(如顶部进度条),避免整块替换。

数据获取分层:请求应在渲染外发起(RSC 服务端获取、缓存 promise),渲染期 use()/读取缓存挂起;预取(prefetch)让"即将查看的内容"提前进入缓存,切换时 Transition 内立即命中;fallback 只在"无缓存 + 首次"出现,配合流式 SSR 的增量填充;错误仍由 ErrorBoundary 处理(不显示 fallback)。边界:Transition 内的挂起不保证"永不显示 fallback"(缓存失效且新请求慢时仍可能挂起,靠预取与缓存控制概率);过渡期间的旧 UI 与后台新数据渲染同时进行,主线程压力需时间切片消化;Suspense Transition 与并发渲染协作时,旧的过渡可被新输入打断(丢弃过期结果)。

答题核心是"Transition 中挂起保留旧 UI、避免闪烁",再讲预取与缓存让切换命中、fallback 只用于首载,最后给"不保证永不闪烁"与可打断的边界。

#
★★

21. React Compiler 在 Server Components 的自动记忆化协作

React Compiler 在 Server Components 中如何协作?自动记忆化对 RSC 有什么价值?

  • RSC 编译与记忆化的兼容
  • 服务端渲染的重复计算消除
  • 与数据获取/流式的边界

React Compiler 可以编译 Server Components:RSC 也是普通函数组件(在服务端执行),Compiler 的响应式依赖分析与记忆化生成同样适用,产物(含缓存指令)在服务端执行。价值:服务端渲染中"同请求内重复渲染"的重复计算被消除(如列表项组件被多次实例化时的公共派生计算)、props 引用稳定(多个 RSC 边界共享数据时减少服务端重算);RSC 的数据获取(async 组件、服务端 fetch)不在"缓存推导"范畴(Compiler 记忆化的是渲染计算而非请求),但依赖"读取的 props"的派生渲染被正确缓存。

协作边界:RSC 在服务端执行、无客户端水合,Compiler 的记忆化"渲染级缓存"在服务端同样按渲染生命周期管理(不跨请求缓存——跨请求缓存是 use cache 的职责,两者正交);Server Function/Server Actions 等"指令函数"不受记忆化影响;RSC 的可序列化 props 由 Compiler 无关的边界机制保证(编译器不做序列化分析);流式 SSR 中 Suspense 边界的多次恢复渲染(同一请求内)会复用记忆化缓存,减少恢复开销;注意 RSC 的异步组件(async function)编译支持按版本确认(官方支持后生效),不符合规则的读取(如渲染期读请求级可变对象)仍会 bailout。整体:Compiler 对 RSC 的收益是"服务端渲染更快",不影响客户端交付语义。

答题先讲 RSC 可编译与记忆化的服务端价值(重复计算消除、恢复渲染复用),再划清"不跨请求缓存、不替代 use cache、不处理序列化"的边界。

#
★★

22. React 19 的 batching(批处理)更新在事件回调与定时器的边界

React 19 的自动批处理(Automatic Batching)在事件回调与定时器中的边界是什么?

  • 自动批处理覆盖的范围(事件、promise、定时器)
  • 批处理的合并语义与渲染次数
  • 需要立即渲染的边界(flushSync)

React 18+(19 延续)的自动批处理把"同一执行上下文内的多次 setState"合并为一次渲染,覆盖范围扩展到所有场景:React 事件回调、原生事件、setTimeout/setInterval、Promise 回调(then/catch/finally)、async 函数体内、fetch 回调等——legacy 模式只在 React 事件内批处理,React 19 全面自动批处理。合并语义:多次 setState 按调用顺序应用(函数式更新排队基于最新值),只触发一次 render 与 commit,减少渲染与 DOM 提交次数;批处理在"微任务边界"内生效,跨宏任务(await 之后新宏任务)可能产生新批次(实际在 await 前的同步段内已批处理完成)。

边界:需要"更新后立即读 DOM"时(测量布局、命令式交互),批处理导致 DOM 尚未提交——用 flushSync(() => setState(...)) 强制同步刷新(会打断并发渲染,谨慎使用);React 19 中 flushSync 仍保留;批处理不改变"函数式更新基于最新值"的语义;Transition 内的更新与批处理协作(transition 内多次更新合并后按低优调度);错误边界与批处理:一个批次内组件抛错,整个批次回滚重试;理解批处理能解释"连续 setState 为什么只渲染一次"与"为什么在 flushSync 里可以读到最新 DOM"。

答题先讲自动批处理的覆盖范围(所有异步上下文)与合并语义(一次 render/commit、函数式更新排队),再讲 flushSync 强制同步的边界与"微任务内生效"的细节。

#
★★

23. React 19 的 Transition Tracing API 在性能监控的工程应用

React 19 的 Transition Tracing API 是什么?它在性能监控中的工程应用与边界是什么?

  • Transition Tracing 的指标与触发
  • 与监控平台集成的模式
  • 使用边界(实验性、开销)

Transition Tracing API(实验性)让应用在"Transition 更新完成时"收到回调与元数据,用于测量"从发起 Transition 到渲染完成"的端到端时长:通过 createRoot 的 onTransitionComplete/onTransitionProgress 等选项或 profiling 专用入口注册,回调携带 transition 的标记(name)、持续时长、涉及的 Suspense 边界信息等。工程价值:把"后台更新是否拖慢体验"变成可观测指标——例如"列表过滤 Transition 的 p95 完成时长"超阈值报警,配合 Performance Tracks 定位瓶颈;区分"紧急更新延迟"与"Transition 完成延迟"两类预算。

应用模式:开发/灰度环境采集(Production Profiling 构建支持),指标上报到监控平台(与 RUM 数据关联用户操作上下文);结合 isPending 记录"用户可见等待时长";对高频 Transition(搜索、筛选)建立"完成时长基线",回归告警;边界:API 仍标记实验性(后续版本可能调整),生产接入需锁定版本与 profiling 构建(普通生产构建不输出 tracing 数据);有采集开销(profiling 构建本身开销),采样上报;Transition Tracing 度量"更新完成",不替代"绘制/交互"指标(INP 需 Performance Observer),两者互补。

答题先讲 API 的形态(root 选项注册、完成回调与元数据)与度量对象(Transition 端到端时长),再讲监控集成与基线告警模式,最后给实验性、开销与指标定位的边界。

#
★★

24. React Compiler 对 useEffect 与 useMemo 的依赖推断的工程价值

React Compiler 对 useEffect 与 useMemo 的依赖推断有什么工程价值?推断的依据是什么?

  • 对 effect 依赖的自动补全
  • 对 useMemo 缓存的自动生成
  • 消除手工维护的工程价值

Compiler 对 useEffect 推断其回调闭包读取的所有响应式值并自动补全依赖数组,对 useMemo 推断工厂函数的响应式依赖并自动生成缓存逻辑(依赖未变跳过计算)。推断依据是"读取路径分析":编译器在组件作用域内追踪 props/state/context 及其派生值(经函数调用、字段访问、解构)的流动,凡"渲染期读取且可能变化"的值即为响应式依赖;读取稳定值(ref.current、模块常量、useCallback 返回的稳定引用)不产生依赖。工程价值:消除手工维护依赖的两类痛点——漏依赖(stale closure 根源)与冗余依赖(导致 effect 频繁重跑/缓存失效),编译器保证"闭包读取 ⊆ 依赖数组"且"依赖最小化"。

进一步价值:依赖正确性从"lint 警告 + 人肉修改"变为"编译期保证",开发效率与正确性双提升;useMemo 自动缓存让"该不该 memo"的决策消失;React 19 的 lint 与 Compiler 对齐(exhaustive-deps 建议移除冗余依赖);边界:推断失败(动态访问、外部可变)时该处 bailout,依赖退回手工维护;显式依赖意图(故意依赖某值实现重跑)Compiler 尊重或提示;ref 语义(变化不触发渲染)使 ref 不进入依赖,符合 React 模型。工程上"依赖交给编译器"意味着代码评审重点从依赖数组转向数据流设计。

答题先讲推断机制(读取路径分析、响应式 vs 稳定值),再讲消除漏/冗余依赖的工程价值与 lint 对齐,最后给 bailout 与显式意图的边界。

#
★★

25. React 19 的 useTransition 与 React Compiler 的协作边界

useTransition 与 React Compiler 如何协作?协作的边界是什么?

  • Transition 语义与记忆化的兼容
  • 自动记忆化对过渡渲染的加速
  • 协作边界(渲染纯度、依赖)

useTransition/startTransition 定义"更新优先级",React Compiler 定义"渲染成本"——两者正交且互补:Transition 让非紧急更新可被打断、按时间片执行;Compiler 的记忆化减少每次过渡渲染中的重复计算(依赖未变的组件跳过重渲染、值跳过重算),使"被打断后恢复"的过渡更快完成,也让时间片内完成更多工作。协作点:Transition 内多次 setState(同一过渡)自动批处理,Compiler 缓存与批处理兼容(缓存按渲染计算,批处理只影响提交次数);过渡中组件挂起恢复(Suspense)时,Compiler 缓存让恢复渲染复用先前结果。

边界:Compiler 要求渲染纯函数,Transition 不豁免该要求(渲染期副作用在过渡中同样危险且会 bailout);useDeferredValue 的延迟值作为响应式依赖参与 Compiler 分析(值变化触发缓存失效,符合预期);isPending 读取本身是响应式值(其变化触发重渲染,Compiler 正常记忆化);Transition 内"依赖渲染次数"的代码(如计数器)在 Compiler 下可能因缓存跳过而行为变化(本就违反渲染纯度,属规范内);Compiler 不改变 Transition 的可打断性与优先级语义,不优化的 bailout 组件在过渡中仍可正常打断。工程上:两者叠加是"并发响应 + 低成本渲染"的完整性能方案。

答题先讲"优先级与成本正交互补"的协作模型,再讲批处理、Suspense 恢复与延迟值依赖的协作点,最后给渲染纯度要求与优先级语义不变的边界。

#
★★

26. React 19 的 API(实验)在隐藏组件渲染的工程应用

React 19 的 API(实验)在隐藏组件渲染中有哪些工程应用?与 Activity 的关系是什么?

  • Offscreen 的隐藏渲染机制
  • 与 Activity 的演进关系
  • 保活与预渲染的应用场景

(实验 API,Activity 的前身)允许把子树渲染"离开屏幕":mode="hidden" 时子树渲染但不对用户可见(DOM 以 hidden 处理),state 保留、滚动位置保留;mode="visible" 恢复正常显示。它与 Activity 的关系:React 19.2 将 Offscreen 正式化为 (保留了核心机制并扩展了 Effect/后台更新的可配置语义),工程上 Offscreen 的应用模式即 Activity 的应用模式——保活(Tab 工作台、路由缓存)、预渲染(提前渲染"即将访问"的内容,切过去即显)、模态框底层面板保留。

工程应用要点:hidden 子树默认停止渲染更新(可配置后台更新),省 CPU;state 保留避免重新挂载(表单草稿、筛选条件不丢);与 Suspense 协作:Offscreen 内可含 Suspense 边界(隐藏时挂起不影响可见 UI);隐藏树中 useEffect 默认暂停(React 19.2 可配置),需注意依赖"持续运行"的逻辑(定时器、轮询)在隐藏时不执行(符合保活资源治理目标);成本:隐藏子树的 DOM 与内存仍在(非卸载),保活数量需设上限;实验阶段 API 可能调整,生产使用需锁版本或等 Activity 稳定。工程判断:用 Offscreen/Activity 保活的收益(切换零延迟、状态保留)vs 成本(常驻内存、隐藏期仍占用资源)按场景评估。

答题先讲 Offscreen 的隐藏渲染机制与 Activity 的演进关系,再讲保活/预渲染应用与"隐藏暂停 effect、DOM 常驻"的工程要点,最后给成本评估。

#
★★

27. React Compiler 在大型组件库的逐步启用策略与代码评审

大型组件库如何逐步启用 React Compiler?代码评审中应关注什么?

  • 按包/按目录的增量启用
  • 启用顺序与风险控制
  • 评审关注点(规则合规、bailout、行为)

大型组件库的启用策略:先工具准备(安装 babel-plugin-react-compiler + eslint-plugin-react-compiler + runtime,配置 compilationMode 支持按目录/包范围);再"lint 先行"——全量跑 eslint 报告,修复 Rules of React 违规(渲染期副作用、外部可变读取),统计 bailout 原因;随后按"低风险 → 高价值"顺序增量启用:先启用无副作用、纯展示的组件包(收益直观、风险低),再启用含数据流的业务组件,最后处理含 ref/第三方交互的高风险包;每个阶段跑全量行为回归(重点:渲染时序依赖、ref 滥用、与第三方库交互的组件)与性能对比(渲染耗时、重渲染率)。

代码评审关注点:新增代码是否违反 Rules of React(评审从"功能正确"扩展到"可编译性"——编译报告作为 CI 检查项);bailout 原因是否合理(新增代码应尽量零 bailout);'use memo'/'use no memo' 指令的使用是否必要(避免滥用逃逸门);依赖数组是否已按 Compiler 语义简化(评审不再要求手工 exhaustive-deps 补全,反而要求移除冗余);性能关键路径的组件结构(避免评审"该不该 memo"转为"数据流是否清晰");回滚预案:编译开关集中配置,灰度异常一键关闭。度量:编译成功率、bailout 率、性能门禁三项作为启用阶段的质量门槛。

答题按"lint 先行 → 按风险分层增量启用 → 回归与性能门禁"的策略展开,再给评审关注点(规则合规、bailout 治理、指令使用、依赖简化)与回滚预案。

#

28. React Compiler 对 class 组件的兼容性边界与迁移路径

React Compiler 对 class 组件的兼容性边界是什么?迁移路径如何设计?

  • class 组件不参与自动记忆化
  • 手工优化保留的边界
  • 逐步迁移的策略

React Compiler 不编译 class 组件(其分析基于函数组件与 Hooks 模型,class 的生命周期/this 语义无法映射到响应式依赖推导):class 组件保持原行为、无自动记忆化,其内部的手工优化(shouldComponentUpdate、React.PureComponent、memo 包裹类组件)继续有效。兼容性边界:class 组件与函数组件的混合使用正常(Compiler 只跳过 class 本身);class 组件的 props 传入仍由"调用方"的记忆化控制(函数组件父级给 class 子组件传稳定引用);React 19 对 class 组件保持完整支持(生命周期、错误边界等),Compiler 的缺席不构成功能问题。

迁移路径:按"交互复杂度与性能敏感度"排序——先迁移纯展示/纯数据流的 class 组件(函数化收益明确),后迁移生命周期繁重或 this 密集型组件(需映射 componentDidMount→useEffect、componentDidCatch→错误边界类等语义,注意生命周期到 effect 的时序差异:getSnapshotBeforeUpdate→useLayoutEffect、componentWillUnmount→cleanup);迁移中可用 class 组件"包装"不迁移的遗留代码(函数组件壳 + class 内核)降低风险;性能敏感的 class 组件在迁移前保留手工优化(SCU/memo),迁移后交给 Compiler;以"迁移后行为回归 + 性能对比"验证每批迁移,不追求一次性全量。

答题先明确"class 不编译、手工优化保留"的边界与混合使用兼容,再给按复杂度排序的迁移路径与生命周期映射要点,最后以回归验证收尾。

#

29. 如何按包设置 compilationMode、收集编译成功率与 bailout 原因,并用性能回归门禁和快速回滚替代一次性全量开启

如何按包设置 compilationMode、收集编译成功率与 bailout 原因?如何用性能回归门禁和快速回滚替代一次性全量开启?

  • compilationMode 的粒度配置
  • 成功率与 bailout 原因的采集
  • 门禁与回滚机制

compilationMode 配置:Babel 插件的 compilationMode 支持 'annotation'(仅编译带指令的组件)、'all'(全部组件)、以及按路径/目录的过滤器(通过插件配置的 includes/excludes 或 per-file 编译脚本),实现"按包启用"——先对某个包目录启用,其余包保持原状;粒度设计:以"包的边界"(目录/入口)为单位,因为包是发布与回滚的最小单元。采集编译成功率与 bailout 原因:构建脚本输出"编译通过数/总组件数、成功率";bailout 原因由编译器诊断(violation 类型 + 文件/组件位置)汇总成报告(JSON/日志),CI 保存历史对比趋势;配套 eslint-plugin-react-compiler 在开发期提示。

门禁与回滚:性能回归门禁——CI 中运行基准(Profiler 度量关键页面的提交耗时/重渲染数,或 Puppeteer 跑核心流程收集 INP 样本),对比启用前后基线,超阈值(如 p95 提交耗时 +20%)阻断合并;正确性门禁——行为回归测试(组件测试 + E2E)全绿;快速回滚——编译开关集中(环境变量/配置项),异常时一键关闭回退到未编译产物(产物与源码等价,回滚零迁移成本),灰度采用"按包 + 比例"(先内部包、再低流量路由),监控错误率与性能指标决定继续/回滚。原则:小步、可度量、可撤销,替代"全量一次性开启"的高风险路径。

答题按"compilationMode 按包配置 → 成功率与 bailout 报告采集 → 性能门禁 + 一键回滚 + 灰度"三步策略展开,突出小步可撤销的工程方法论。

#

30. React Compiler 无法证明某段代码符合 Rules of React,或调用了不可编译的第三方组件时,会怎样跳过优化

React Compiler 无法证明代码符合 Rules of React 或调用不可编译的第三方组件时,如何跳过优化?具体机制是什么?

  • bailout 的判定与范围
  • 第三方调用的边界行为
  • 跳过优化的可观测性

Compiler 对每个组件独立判定:若静态分析发现代码违反 Rules of React(渲染期副作用:写模块变量/全局、渲染期修改 ref 或 DOM;读取不可分析的外部可变状态;动态访问/元编程等无法推导依赖的模式),则该组件 bailout——不生成任何记忆化代码,编译产物与原源码行为完全等价(无缓存指令),功能不受影响,仅失去优化。调用不可编译的第三方组件时:Compiler 对"调用点"本身建模(props 引用稳定会被记忆化),但不对第三方内部做任何假设与分析——第三方内部是否可编译与调用方无关;若第三方的"使用方式"(如把回调存到模块级)使调用方代码无法证明安全性,调用方组件会整体 bailout。

跳过优化的机制细节:bailout 是"组件级"的(该组件内全部不优化,而非部分优化);诊断输出(构建日志/CLI)记录 bailout 组件、违规类型与代码位置;开发期 eslint-plugin-react-compiler 提前给出同样诊断(不阻断);开发者可用 'use no memo' 显式声明"此组件不优化"(主动跳过,明确意图),或 'use memo' 断言"必须优化"(编译失败时构建报错,用于关键路径强约束);跳过优化不影响应用其余部分(其它组件照常优化)。治理:修复违规(副作用移 effect、外部状态改用受控读取)可让组件重回优化路径。

答题先讲 bailout 的判定(违反 Rules of React 或不可分析模式)与"行为等价、仅失优化"的语义,再讲第三方调用的边界建模与组件级范围,最后给诊断可观测性与 'use memo'/'use no memo' 管控。

#

31. 何时应该使用 'use no memo' 退出 React Compiler

何时应该使用 'use no memo' 退出 React Compiler?退出后如何管理该组件的性能?

  • 'use no memo' 的语义与位置
  • 需要退出的典型场景
  • 退出后的性能管理

'use no memo' 是组件级指令(放在组件/自定义 Hook 文件顶部或函数体内),告诉 Compiler"该组件不做自动记忆化"——与 bailout(编译器被动跳过)不同,它是开发者主动选择退出。典型使用场景:性能不敏感且"完全不需要缓存"的组件(简化产物、避免缓存检查开销);与第三方系统交互、依赖"每次渲染创建新引用"的组件(某些非 React 库以引用变化为信号);渲染"极其便宜"且被高频更新的组件(缓存成本大于收益);调试/对比场景(验证某组件是否因记忆化引入问题);以及"临时隔离":定位问题期间对可疑组件退出,确认后再决定修复还是永久退出。

退出后的性能管理:'use no memo' 只影响该组件的自动记忆化——它自身的重渲染照常(依赖其 props 的调用方仍被记忆化),子组件重渲染取决于 props 引用稳定性(若该组件每次渲染新建对象传给子组件,子组件会重渲染);需要时用手工 memo/useMemo 兜底该组件内部;注意:'use no memo' 不是"跳过规则检查"(违规代码仍会被诊断);官方建议先尝试修复违规而非直接退出;退出范围最小化(个别组件,而非整文件除非确认);配合 Profiler 验证退出前后差异。语义对比:'use memo' 强制优化(失败报错)、'use no memo' 强制退出(禁止优化),默认是"能优化则优化"。

答题先讲 'use no memo' 的主动退出语义与典型场景(引用变化依赖、极廉价组件、调试隔离),再讲退出后的性能管理(调用方记忆化、手工兜底)与最小化使用原则。