陌上人如玉
公子世无双

12.14 如果让你设计 React

12.14 如果让你设计 React

一句话:设计一个 UI 框架,本质是回答五个问题——「怎么描述 UI(元素树)→ 怎么高效更新(协调)→ 怎么组织逻辑(组件/Hooks)→ 怎么调度(优先级/并发)→ 怎么与宿主交互(host config)」——而 React 的每个「奇怪设计」都能从「它选择了哪种取舍」反推出来。

一、先立「第一性原理」

⭐⭐⭐ 三条不可绕开的前提(任何 UI 框架都要面对):

① 【「UI 是状态的函数」】
   ⭐ `UI = f(state)` —— 「同一份状态 → 同一份 UI」(**声明式**替代「命令式操作 DOM」)
   ⭐ 这是「一切设计的起点」:它带来「可预测、可测试、可组合」,也带来
     「每次状态变都要「重新算一遍 UI」(⭐ 所以「怎么算得便宜」成了核心问题)」。

② 【「DOM 操作是昂贵的」】
   ⭐ 「重新创建整棵 DOM」不可接受 → 所以要么「找出差异」(diff),
     要么「精确知道哪个值变了」(细粒度订阅,见 12.13),
     要么「编译期算出来」(编译时优化)。
   ⭐ 这三条路 = 「React / Signals / Svelte 的分野」。

③ 【【单线程 + 用户感知】】
   ⭐ 「计算」和「用户输入」抢同一个线程 →
     所以「长计算要能被「打断/让路/分片」」→ ⭐ **这就是「并发渲染」的动机**。

二、五个必须回答的问题

问题 1:怎么描述 UI

⭐⭐⭐ 候选方案与取舍:

   【A. 模板(`<div>{{x}}</div>`)】
     ✅ 「接近 HTML」、对编译器友好(⭐ Svelte/Vue 走这条)
     ⚠️ 「表达能力受限」(要额外语法)、「类型推断难」(⭐ 要靠工具链补救)
   【B. 函数调用 / JSX(`jsx('div', {}, x)`)】⭐ React 选这条
     ✅ **「就是 JS」**——「表达式、组合、泛型、类型」全都直接可用
     ⚠️ 「需要编译」(JSX → 函数调用);⚠️ 「容易「每次渲染都创建新对象」」
   【C. 字符串模板(运行时解析)】
     ✅ 「无需编译」;⚠️ 「运行时开销 + 类型不安全」→ 现代框架基本淘汰

   ⭐ React 选 B 的深层理由「与「问题 3(怎么组织逻辑)」连在一起」——
     它想让「UI 描述」与「逻辑」用「同一种语言」(JS),
     ⭐ 因为「UI 里天然混着逻辑」(条件、循环、映射),
       而「模板语言必然要「再造一套控制流」」(⭐ 见 `v-if`/`v-for`)。
⭐⭐⭐ 「元素」的设计(⭐ 这是 React 最容易被误解的一层):

   元素 = 「一个普通的、不可变的对象描述」(`{ type, props }`)
   ⭐ 关键决定:
     ① 【「元素」不是「组件实例」】⭐
        「每次渲染都创建新元素对象」→ 所以「比较元素」要靠「diff」(而不是「复用对象」)
        ⭐ 这是「React 的 diff 存在的原因」,也是「为什么「每次渲染创建新对象」是正常的」
     ② 【「组件」是「函数」】⭐
        函数组件 = 「一个「props → 元素」的纯函数」
        ✅ 「易测试、易组合、易推理」;⚠️ 「每次渲染都是一次函数调用」(⭐ 所以有 Hooks 的顺序问题)
     ③ 【「组件树」是「嵌套」的(不是「图」)】
        ⚠️ 这带来「跨层传递要靠 props/Context」(⭐ 成本)
        ✅ 换来「可预测的更新顺序」与「简单的协调算法」

问题 2:怎么高效更新

⭐⭐⭐ 三条路线(⭐ 这是「框架分野」的核心):

   【路线 A:Virtual DOM + diff】⭐ React 选这条
     ① 生成新元素树 → ② 与旧树「同层比较」→ ③ 得出「最小变更集」→ ④ 应用
     ✅ 「心智模型简单、不必关心「哪些值变了」」(⭐ 你只管「写 UI」)
     ✅ 「可以做「跨平台的抽象」(⭐ 因为「树 + diff」与宿主无关,见问题 5)
     ⚠️ 「diff 有成本」(要「遍历树」+ 「比较 props」)
     ⭐ 优化手段:「`key`(帮助「识别同一元素」)、`memo`(剪枝)、
       「不可变数据(⭐ 引用比较 O(1) 就能判断「变没变」)」
   【路线 B:细粒度订阅(Signals)】⭐ Solid/Vue/Svelte 5
     ✅ 「不读就不订阅」→ 「无额外 diff」
     ⚠️ 「需要「响应式值」的概念」+ 「与「快照式渲染」冲突」(⭐ 撕裂,见 12.13)
   【路线 C:编译期「静态分析 + 精确更新」】⭐ Svelte
     ✅ 「编译期知道「哪个表达式对应哪个 DOM 节点」」→ 「运行时几乎零 diff」
     ⚠️ 「动态性受限」(⭐ 「运行时动态拼装的结构」难以优化)

   ⭐⭐ React 选 A 的两个「非性能」理由(⭐ 这点最容易被忽略):
     ① ⭐ **「不必让使用者理解「依赖追踪」」**——你不需要「声明「这个 UI 依赖哪些值」」,
        所以「心智模型小」(⭐ 代价是「框架要更努力地优化」)
     ② ⭐ **「与宿主解耦」**——「树 + diff」可以直接复用到
        「Native(RN)/ Canvas / 文档(react-pdf)/ 测试(react-test-renderer)」(⭐ 见 10.15)
        ⚠️ 「Signals/编译期方案」都「更绑定 Web DOM」
⭐ 「diff 的关键设计」(⭐ 面试常问「为什么「同层比较」+「为什么需要 key」」):

   ① 【同层比较(不做「跨层移动」的检测)】
      ⚠️ 因为「判断「一棵子树是否被移动到另一处」是 O(n³)」(⭐ 通用树编辑距离)
      ✅ React 的取舍是「只看「同一层」」→ 算法降到 O(n)(⭐ 见 4.4)
      ⭐ 代价:「跨层移动」会「被当成「删除 + 新建」」(⚠️ 丢状态)
   ② 【`key`:「让「跨渲染的同一元素」被识别出来」】
      ✅ 「列表重排」时「复用 DOM 与状态」(⭐ 否则「就地更新」导致错位,见 5.5)
   ③ 【「类型不同 → 整棵重建」】
      ✅ 因为「`div` 与 `span` 的属性/事件语义完全不同」→ 「重建比「改」更简单可靠」

问题 3:怎么组织逻辑

⭐⭐⭐ 候选方案(⭐ 这条演化史最能说明「取舍」):

   【A. 类组件 + 生命周期】⭐ React 早期
     ✅ 「把「状态 + 逻辑 + 渲染」放在一起」(⭐ 比「模板 + 全局函数」好得多)
     ⚠️ 「逻辑被「生命周期」切碎」:
        「同一个功能」的代码「散落在 componentDidMount/Update/Unmount」→
        ⭐ 「难以复用、难以理解」
   【B. 混入(Mixin)】
     ⚠️ 「来源不明、命名冲突、隐式依赖」→ 「被抛弃」
   【C. HOC / Render Props】
     ✅ 「能复用逻辑」;⚠️ 「嵌套地狱」+ 「props 来源不明」
   【D. Hooks】⭐ 现在
     ✅ ⭐ **「按「功能」聚合逻辑」(而不是「按生命周期切碎」)**
     ✅ 「复用是「函数级」的(不必「包一层组件」)」
     ⚠️ ⭐ **代价:「必须「按顺序无条件调用」」**——
       因为「Hooks 的值存在「fiber 上的数组」里,靠「调用顺序的下标」定位」(见 11.11)
     ⚠️ 「闭包陷阱」(⭐ 每次渲染有独立的闭包,见 2.13)
   【E. 「编译期响应式」(Svelte 5 runes / Solid)】
     ✅ 「不需要「Hooks 顺序」规则」(⭐ 因为「值在编译期被转成 signal」)
     ⚠️ 「与「纯 JS 语义」有距离」(⭐ 要理解「编译后的东西」)

   ⭐ React 选 D 的原因:「**保留「纯 JS 的函数语义」**」(⭐ 不引入「编译期魔法」)
     并「接受「顺序规则 + 闭包陷阱」这两个代价」。
   ⭐ 而且「Hooks 可测试」——「自定义 Hook」就是「一个函数」,
     可以「单独测」(⭐ 与「组件测试」解耦)。

问题 4:怎么调度

⭐⭐⭐ 这是 React 「最难被替代」的地方(⭐ 也是「为什么 React 还在用 VDOM」的原因之一):

   【要解决的真实问题】
     · ⭐ 「用户输入必须「立刻响应」」(哪怕「后台正在渲染一个大列表」)
     · ⭐ 「不同更新「优先级不同」」(输入 > 点击 > 数据刷新 > 预加载)
     · ⭐ 「一次渲染可能「很久」」→ 要能「打断、让路、稍后继续」

   【方案 A:同步渲染(最早期)】
     ⚠️ 「一次渲染必须「一口气做完」」→ 「大列表渲染时「输入卡成 PPT」」
   【方案 B:时间切片 + 优先级(Concurrent)】⭐ React 18 走这条
     · ⭐ 「把渲染「切成小片」」(每片约 5ms,见 4.17)
     · ⭐ 「每片后检查「是否有更高优先级的工作」」(有就「让路」)
     · ⭐ 「不同更新进不同「车道(Lane)」」
     ⚠️ 代价:「渲染可能「被丢弃并重来」」→ ⭐ **「渲染必须是纯函数」**(⭐ 这条「反过来约束了一切」)
   【方案 C:细粒度更新「天然不需要调度」】
     ✅ 「更新一个值只改一个节点」→ 「本来就不会「一次做很久」」
     ⚠️ 但「首屏渲染」「结构性变化」「数据获取」仍然要「调度」——
       ⭐ 所以「Signals 方案也要解决「并发/调度」问题」(⭐ 这是两者都在做的部分)

   ⭐⭐ 「时间切片」的实现前提(⭐ 面试加分点):
     · ⭐ 「渲染过程的遍历状态要「能从「递归栈」里搬出来」」
       → 所以「fiber 用「链表(child/sibling/return)」+「全局指针」」
       → ⭐ 「这样才能「随时保存并返回」」(见 4.2/11.11)
     · ⭐ 「让出主线程的机制」要「比 setTimeout 更及时」
       → 所以用 **`MessageChannel`**(见 4.17)
   ⭐ 一句话:「Concurrent 逼出了 Fiber 的「链表结构」与 Scheduler 的「MessageChannel」」。

问题 5:怎么与宿主交互

⭐⭐⭐ 「宿主配置(host config)」这个抽象(⭐ 这是 React 最有远见的设计之一):

   把「reconciler(协调器:diff / Fiber / 优先级 / Hooks / Suspense)」
   与「具体平台的「渲染操作」」解耦:
     · ⭐ **Web**:`createElement` / `appendChild` / `setAttribute`
     · ⭐ **Native**:创建原生视图(`UIView` / `android.view.View`)
     · ⭐ **Canvas / WebGL**:命令式的绘制调用
     · ⭐ **文档**:生成 PDF 元素
     · ⭐ **测试**:生成「可断言的树」(⭐ 所以「组件测试可以「不跑真 DOM」」)

   ⭐ 收益:
     ① ⭐ **「一次实现,多端复用」**(⭐ RN 的全部基础,见 10.15)
     ② ⭐ **「可测试性」**(⭐ `react-test-renderer` 就是「一个假的 host config」)
     ③ ⭐ 「自定义渲染目标」成为可能(`react-three-fiber` 等)

   ⚠️ 代价:「抽象层「隔掉了「平台的直接控制」」」——
     ⚠️ 「极端性能场景」下(如「游戏级渲染」)要「绕开 React」
     (⭐ 所以「Canvas 里用命令式引擎 + React 只做外壳」是常见做法)

三、把五个问题串起来:React 的设计「为什么会是这样」

⭐⭐⭐⭐ 一条「因果链」(⭐ 这是本题最有价值的输出):

   「要让「UI = f(state)」在「大应用」里可用」
     → 必须解决「更新效率」→ 选「VDOM + diff」(⭐ 保住「不必理解依赖追踪」+ 保住「跨平台」)
     → diff 需要「不可变数据」→ 「单向数据流 + props 只读」
     → 需要「逻辑复用」→ Hooks(⭐ 换取「按功能聚合」,代价是「顺序规则」)
     → 需要「大更新不卡输入」→ 时间切片 + 优先级(Concurrent)
     → 「可中断」要求「渲染可丢弃/重放」→ ⭐ **「渲染必须纯」**(这条被反复强调)
     → 「纯渲染」又与「细粒度可变响应式(signals)」冲突 → ⭐ 「不上 signals,改走 `useSyncExternalStore` + 编译器」
     → 「渲染要能被中断」→ 遍历状态要「外化」→ ⭐ **Fiber 链表 + 全局指针**(见 4.2)
     → 「让出主线程」要可靠 → ⭐ **`MessageChannel` + 时间片**(见 4.17)
     → 「多个平台要复用」→ ⭐ **host config 抽象**(见 10.15)
     → 「开发者不该手动管 memo」→ ⭐ **React Compiler**(见 4.15)

   ⭐ 你会发现:「**React 的每一个「奇怪设计」都是「某个约束的必然结果」**」——
     这就是「设计一个框架」与「背 API」的本质区别。
⭐⭐⭐ 「如果重来一次,哪些会不一样」(⭐ 展示「批判性思考」):

   ① ⭐ **「Hooks 的顺序规则」也许能避免**
      如果「值不是「存在 fiber 的数组里」」而是「绑定到「编译期确定的槽位」」(⭐ Compiler 已经在做类似的事)
   ② ⭐ **「Context 的粒度太粗」**
      当前「一变全量重渲染」(见 5.9)→ 也许「选择器式订阅」应该更原生
   ③ ⭐ **「`useEffect` 被过度使用」**
      「数据获取」+「派生计算」都该「不在 Effect 里」(⭐ React 官方文档也在强调这个,见 8.1)
   ④ ⭐ **「并发渲染的默认开启」来得太晚**
      「很多库的开发模式」建立在「同步渲染」上 → 迁移成本高
   ⑤ ⭐ **「Server Components 的引入方式」有争议**
      「'use client' 的边界」对「生态库的作者」是「全新的约束」(见 4.12)
   ⭐ 这些问题「正是 React 团队现在在「新版本 + Compiler」里解决的」——
     ⭐ 所以「设计框架」不是「一次做完」,而是「在约束下持续演化」。

四、面试怎么答(⭐ 结构化输出)

⭐⭐⭐ 一个「高分答法」的四段结构:

   ① 【先立框架(⭐ 30 秒内说清楚「要回答哪几个问题」)】
      「设计一个 UI 框架要回答五个问题:
        「怎么描述 UI、怎么高效更新、怎么组织逻辑、怎么调度、怎么与宿主交互」。」

   ② 【逐题给「选项 + 取舍」(⭐ 不要「只讲一种方案」)】
      每个问题「至少说两条路线 + 各自的代价」:
        · 描述 UI:模板 vs JSX(⭐ React 选 JSX → 「UI 与逻辑同语言」+ 「不必重造控制流」)
        · 更新:VDOM+diff vs 细粒度订阅 vs 编译期(⭐ React 选 diff → 「不必理解依赖追踪」+「跨平台」)
        · 逻辑:类 vs Hooks(⭐ Hooks 的代价是「顺序规则 + 闭包陷阱」)
        · 调度:同步 vs 时间切片 + 优先级(⭐ 这是 React 最难替代的部分)
        · 宿主:host config(⭐ 一次实现多端复用 + 可测试)

   ③ 【串成因果链(⭐ 这是「加分项」)】
      ⭐ 「因为要「可中断」→ 渲染必须能被丢弃重放 → 渲染必须纯 →
         所以不上 signals;因为渲染要能中断 → 遍历状态要外化 → 所以 Fiber 用链表;
         因为要让出主线程 → 所以用 MessageChannel」

   ④ 【给「如果重来会怎么改」(⭐ 展示批判性)】
      「Hooks 的顺序规则、Context 粒度、Effect 的过度使用、并发默认开启的时机」
      —— ⭐ 并指出「这些正是 React 现在通过 Compiler/新版本在解决的」。

五、本篇特有的坑(表达层面的)

① 「只讲 React 做了什么,不讲「为什么」(⭐ 那是「背 API」不是「设计」)
   ✅ 每个设计都要能回答「它解决了什么问题、代价是什么」

② 「把「VDOM 更快」当成前提」(⭐ 不准确)
   ⭐ 「VDOM 本身是「更慢」的」(多一层 diff);
     它的价值是「**不必理解依赖追踪 + 跨平台 + 可测试**」——
     ⚠️ 「性能优化是「与之配套的工程」」(memo/Compiler/走合成)

③ 「忽略「调度」这一层」(⭐ 只讲 diff/Hooks 会显得浅)
   ⭐ 「Concurrent 是 React 最难被替代的能力」,也是「Fiber 与 Scheduler 存在的原因」

④ 「把「Hooks 的顺序规则」说成「React 的缺陷」」(⭐ 太片面)
   ⭐ 它是「选择「纯 JS 语义(不引入编译期魔法)」的**代价**」——
     而且「Compiler 正在「把「手动 memo」这件事从开发者手上拿走」」

⑤ 「忽略「host config」」(⭐ 这是「RN/react-three-fiber/react-pdf」的全部基础)

⑥ 「不提「撕裂(tearing)」」(⭐ 那是「不上 signals」的技术核心,见 12.13)

⑦ 「把所有「性能问题」都归给「框架」」(⭐ 太简化)
   ⭐ 「DOM 数量、包体积、网络、长任务」都「与框架无关」(见 5.5/5.14/8.10)

⑧ 「不提「取舍的反面」(⭐ 设计题的关键是「代价」)」
   ⭐ 「每个「优点」都要说「它的代价」」——⭐ 这才是「设计」的语汇

⑨ 「把「Signals/Svelte」说成「全面更好」」
   ⭐ 它们的「优势场景」很明确(高频细粒度),但「并发调度/生态/跨平台」上各有取舍

⑩ 「不谈「演化」」(⭐ 框架是「在约束下持续改」的)
   ⭐ 说清「Compiler/RSC/并发」是「怎么在「不换模型」的前提下继续推进的」

六、面试延伸

  1. 「如果让你设计一个前端框架,你会先回答哪些问题?」
    ⭐ 五个:① 怎么描述 UI——模板 vs JSX/函数调用(⭐ React 选后者:「UI 与逻辑同一种语言」,不必「重造控制流」);② 怎么高效更新——VDOM + diff vs 细粒度订阅(Signals)vs 编译期精确更新(⭐ React 选 diff:「不必让使用者理解依赖追踪」+「与宿主解耦」);③ 怎么组织逻辑——类/生命周期 vs Hooks(⭐ Hooks 按「功能」聚合逻辑,代价是「顺序规则 + 闭包陷阱」);④ 怎么调度——同步 vs 时间切片 + 优先级(⭐ 这是 React 最难被替代的部分);⑤ 怎么与宿主交互——⭐ host config 抽象(一次实现多端复用 + 可测试)。⭐ 先立框架再展开取舍,比「直接讲 VDOM」高一个层次。

  2. 「React 为什么用 VDOM 而不是「直接操作 DOM」或「细粒度订阅」?」
    ⭐ VDOM 本身不是「更快」的(多一层 diff),它的价值在三处:① ⭐ 「不必让使用者理解依赖追踪」——你只管「写 UI」(UI = f(state)),框架负责「找差异」;而 Signals 「要求你理解「谁订阅了谁」」;② ⭐ 「与宿主解耦」——「树 + diff」可以复用到 Native / Canvas / 文档 / 测试(⭐ RN 的全部基础,见 10.15);③ ⭐ 「可测试性」——react-test-renderer 就是「一个假 host config」。代价:多一层「diff 成本」+ 「需要「不可变数据」来让「比较变便宜」(引用比较 O(1))」+ 「需要 memo/Compiler/结构优化来「剪枝」」——⭐ 所以 React 的性能故事是「一套配套工程」,而不是「VDOM 本身快」。

  3. 「为什么 React 的「渲染必须纯」这条被反复强调?」
    因为 ⭐ 「并发渲染(可中断)」直接要求它:① 「渲染可以被中断」(让出主线程);② 「中断后可能「被丢弃并「用更高优先级重来」」;③ ⭐ 所以「一次渲染可能被执行 0 次、1 次或多次」。如果渲染有副作用(改外部变量、发请求、读写 DOM)→ 「它会「重复执行」或「产生不一致」」。这条约束「反过来约束了一切」:① 它解释「为什么「不能不上 signals」(signals 是「活的值」,与「快照式渲染」冲突,见 12.13);② 它解释「为什么 StrictMode 要「双调用」(主动暴露不纯的渲染,见 8.5);③ 它解释「为什么 useEffect 要「在提交后异步执行」(副作用不能发生在渲染期)」。

  4. 「Concurrent(并发渲染)为什么难实现?」
    因为它要同时满足三件事:① ⭐ 「渲染要能「随时暂停」」——所以「遍历的状态不能放在「递归调用栈」里」→ ⭐ Fiber 用「child/sibling/return 链表 + 全局 workInProgress 指针」(见 4.2/11.11);② ⭐ 「让出主线程后要「及时」恢复」——setTimeout 有「最小延迟 + 后台节流」、requestIdleCallback 「频率受限 + 兼容差」→ ⭐ 所以用 MessageChannel + 约 5ms 的时间片(见 4.17);③ ⭐ 「优先级要能「插队」」——所以要有「车道(Lane)模型」+ 「可丢弃的「工作中的树」」→ ⭐ 而这要求「渲染是纯的」(否则「丢弃」会留下副作用)。三者互相约束——这就是「为什么「并发」是 React 最难被替代、也最难被抄的部分」。

  5. 「host config 这个抽象的价值是什么?」
    ⭐ 它把「reconciler(diff/Fiber/优先级/Hooks/Suspense)」与「具体平台的渲染操作」彻底解耦——于是「同一套 React」可以驱动:⭐ Web(DOM)、⭐ Native(iOS/Android 原生视图,见 10.15)、Canvas/WebGL(react-three-fiber)、文档(react-pdf)、⭐ 测试(react-test-renderer → 生成可断言的树)。收益:① ⭐ 「一次实现、多端复用」(RN 的全部基础);② ⭐ 「可测试性」(组件测试可以「不跑真 DOM」);③ 「自定义渲染目标」变得可能。代价:⚠️ 「抽象层隔掉了「对平台的直接控制」」——所以「极端性能场景」(游戏级渲染)要「绕开 React」(⭐ 「Canvas 里用命令式引擎 + React 只做外壳」是常见架构)。

  6. 「如果 React 重来一次,你会改什么?」
    五处(⭐ 每处都要说「为什么」,而不是「我觉得」):① ⭐ Hooks 的顺序规则——如果「Hook 的值绑定到「编译期确定的槽位」」(而不是「运行时数组下标」),也许能「允许条件调用」(⭐ Compiler 已经在做类似的事);② ⭐ Context 的粒度——当前「一变全量重渲染」(见 5.9),也许「选择器式订阅」应该更原生;③ ⭐ useEffect 的过度使用——「数据获取」与「派生计算」都不该在 Effect 里(⭐ React 官方文档现在也在强调「你可能不需要 Effect」,见 8.1);④ ⭐ 并发渲染的默认开启时机——很多库建立在「同步渲染」的假设上,迁移成本高;⑤ ⭐ Server Components 的引入方式——「'use client' 的边界」对「生态库作者」是全新约束(见 4.12)。⭐ 注意:「这些「不该改」也有理由」(⭐ 每个设计都「换来了什么」)——所以「改」要说清「代价与收益的再平衡」。

  7. 「为什么说「React 的每个奇怪设计都是某个约束的必然结果」?」
    ⭐ 用一条因果链串:「要让 UI = f(state) 在大应用里可用」→ 必须解决「更新效率」→ 选「VDOM + diff」(保住「不必理解依赖追踪」+「跨平台」)→ diff 需要「不可变数据」→ 「单向数据流 + props 只读」→ 需要「逻辑复用」→ Hooks(代价:顺序规则)→ 需要「大更新不卡输入」→ 时间切片 + 优先级(Concurrent)→ 「可中断」要求「渲染可丢弃重放」→ ⭐ **「渲染必须纯」 → 「纯渲染」与「可变响应式(signals)」冲突 → ⭐ 不上 signals,改走 useSyncExternalStore + Compiler → 「渲染要能中断」→ 遍历状态要「外化」→ ⭐ Fiber 链表 + 全局指针 → 「让出主线程」要可靠 → ⭐ MessageChannel + 时间片 → 「多平台复用」→ ⭐ host config 抽象」。⭐ 这说明:「设计」不是「发明 API」,而是「在约束下做出一致的选择」**。

  8. 「这道题考的是什么?」
    ⭐ 考的是「架构思维」而不是「记忆」:① 能不能从需求出发(「UI 框架要解决什么问题」);② ⭐ 能不能「给选项 + 说代价」(⭐ 只说「React 这样做」是背 API;说「为什么不做另一种、另一种的代价是什么」才是设计);③ ⭐ 能不能「串成因果」(「为什么 Fiber 是链表」「为什么渲染必须纯」「为什么不用 signals」——⭐ 这些是「同一条因果链上的节点」);④ 能不能「批判性思考」(「如果重来会改什么、为什么当初不那么做」);⑤ 能不能「落到工程」(「DOM 数量、包体积、长任务这些「与框架无关」的问题该怎么办」)。⭐ 一句话:「这题答得好不好,不看你「知道多少 API」,而看你「能不能在约束之间做出一致的取舍」」。

一句话速记

设计一个 UI 框架要回答五个问题:① 怎么描述 UI(模板 vs ⭐ JSX/函数调用——React 选 JSX 是为了「UI 与逻辑同一种语言」+「不必重造控制流」);② 怎么高效更新(⭐ VDOM+diff vs 细粒度订阅 vs 编译期精确更新——React 选 diff 的真正理由不是「快」而是「不必让使用者理解依赖追踪」+「与宿主解耦」,代价是「需要不可变数据 + memo/Compiler/结构优化这套配套工程」);③ 怎么组织逻辑(类/生命周期 vs Hooks——⭐ Hooks「按功能聚合」但代价是「顺序规则 + 闭包陷阱」);④ 怎么调度(⭐ 时间切片 + 优先级(Concurrent),这是 React 最难被替代的部分);⑤ 怎么与宿主交互(⭐ host config——「一次实现多端复用(RN/Canvas/PDF/测试)+可测试」,代价是「隔掉平台直接控制」);⭐ 最有价值的输出是「因果链」:UI=f(state) → 需要更新效率 → VDOM+diff → 不可变数据 → Hooks → 并发 → 「可中断」要求「渲染可丢弃重放」→ ⭐ **「渲染必须纯」 → 与 signals 冲突 → 不上 signals(改走 useSyncExternalStore + Compiler)→ 渲染要能中断 → Fiber 链表 + 全局指针 → 让出主线程要可靠 → MessageChannel + 5ms 时间片 → 多平台 → host config」;⭐ 面试的四段结构:「先立「五个问题」的框架 → 每题给「两条路线 + 代价」(⭐ 设计题的关键是「代价」)→ 串成因果链 → 给「如果重来会改什么」(Hooks 顺序规则 / Context 粒度 / Effect 过度使用 / 并发默认开启时机 / RSC 的 'use client' 边界)」;⚠️ 不要「把 VDOM 说成更快」、不要「只讲 API 不讲取舍」、不要「忽略调度与 host config」——这题考的是「在约束之间做出一致取舍」的能力,不是「记住了多少 API」。

赞(0) 打赏
未经允许不得转载:陌上寒 » 12.14 如果让你设计 React

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

微信扫一扫

支付宝扫一扫