Skip to content
错题本 0已完全掌握 0正在读取本地学习状态…
先想一想,再看答案

Fiber 架构与源码思路 ​

29. 为什么 React 需要 Fiber? ​

展开答案与追问

参考答案: 传统递归协调把大量工作压在连续调用栈中,不便暂停后恢复。Fiber 将组件树工作显式表示为可遍历的工作单元,支持按优先级安排、暂停、恢复或丢弃尚未提交的结果。目标是改善复杂更新中的响应性,而非保证每次总耗时更短。

追问与易错点: 来源:Fiber 数据结构。Fiber 不是浏览器线程,也不是真实 DOM。

30. Fiber 节点常见字段有哪些? ​

展开答案与追问

参考答案: type/tag 表示节点类型,key 表示身份;child、sibling、return 描述子节点、兄弟和父级关系;pendingProps/memoizedProps 保存待处理及已记忆属性,memoizedState 保存状态;alternate 连接对应版本,flags/subtreeFlags 记录工作标记,lanes/childLanes 关联更新优先级。stateNode 的含义随节点类型而异。

追问与易错点: 这是源码阅读的概念导航,字段属于内部实现,不能作为业务 API;函数组件的 stateNode 不等于类实例。

31. current 与 workInProgress 的双缓冲如何理解? ​

展开答案与追问

参考答案: current 描述当前已提交树,workInProgress 用来计算候选结果,通常通过 alternate 对应。完成后提交候选树并切换当前引用;未完成候选可以重做或放弃。React 会复用节点与共享结构,不是每次把整棵 DOM 深拷贝两份。

追问与易错点: 可用“当前展示版本 / 正在计算版本”口述;双缓冲并不意味着页面里同时存在两套完整真实 DOM。

32. render 与 commit 两个阶段有什么区别? ​

展开答案与追问

参考答案: render 计算下一棵树及其变更,可在并发工作中中断、重试或丢弃;不能随意产生可观察副作用。commit 将完成结果作用到宿主环境,核心 DOM mutation 与 layout 工作同步完成,通常不在中途让出执行权。passive Effects 的处理另有调度时序。

追问与易错点: 用户可见输出应来自完整提交,不能把 render 的中间结果直接暴露到 DOM。Render 与 Commit

33. beginWork、completeWork 与工作循环怎么理解? ​

展开答案与追问

参考答案: 概念上,beginWork 处理当前节点并决定向哪个子节点继续;子树处理完后 completeWork 完成当前节点相关工作,再找兄弟或返回父级。通过显式指针遍历,React 可以在工作单元之间检查是否需要让出。

追问与易错点: 来源:工作循环。不是每执行一行组件代码都能中断;单个耗时函数仍可能占住主线程。

34. Scheduler、Lane 与 Reconciler 如何分工? ​

展开答案与追问

参考答案: Reconciler 决定组件树应如何更新;Lane 用位集合描述更新类别与待处理工作,帮助选取、合并或跳过更新;Scheduler 为可调度工作安排执行机会与优先级。Lane 与 Scheduler 优先级有关联,但不是一套完全相同的枚举。

追问与易错点: 来源:Lane 实现。不要死背位数或固定毫秒切片,细节会随版本变化。

35. 低优先级更新被跳过,会丢失吗? ​

展开答案与追问

参考答案: React 会保留必要的更新队列信息,在后续合适的优先级处理中重放或基于正确状态重新计算,保持更新顺序语义。函数式 updater 应纯净,因为计算可能被重试。一次被丢弃的是候选渲染结果,不代表业务更新必然丢弃。

追问与易错点: 示意:紧急输入可以先提交,昂贵列表稍后完成;不要把队列算法简化成只保留最后一个赋值。

36. Hooks 为什么依赖调用顺序?内部大致如何保存? ​

展开答案与追问

参考答案: 在函数组件 Fiber 中,常规 Hook 状态按调用顺序关联为内部结构;渲染时依序读取相应状态与队列。条件分支改变调用数量或顺序,会让之后的状态关联错位。useState 的 setter 将更新加入队列,随后在对应渲染中计算。

追问与易错点: 这是常规状态 Hook 的概念模型,不要声称所有 use 开头 API 都创建同一种链表节点。

37. React Diff 为什么不求任意树的最小编辑距离? ​

展开答案与追问

参考答案: 通用树最优编辑代价高,React 通过类型、同级位置与 key 等启发式规则判断复用与替换。不同类型通常重建,同级稳定 key 支持识别移动。它追求可预测的工程成本,不保证全局最少 DOM 操作。

追问与易错点: React 列表协调不等于 Vue 3 的最长递增子序列方案;key 也不能让组件跨任意父级自动保留状态。

38. 如何在 90 秒内完整回答 Fiber? ​

展开答案与追问

参考答案: 先说痛点:长任务影响交互;再说结构:Fiber 显式记录工作单元与树关系;接着说过程:current/workInProgress 计算候选树,render 可中断,commit 完成宿主变更;最后说调度:Lane 表达优先级,Scheduler 安排执行机会,并发不是多线程。

追问与易错点: 加分边界:不能中断单个正在执行的长函数;不能在 render 做副作用;底层实现不属于稳定公开 API。

速查表与代码示例 ​

常见 API 速查 ​

API主要用途面试易错点
useState / useReducer保存状态 / 集中状态转换setter 不修改当前渲染快照;reducer 要纯净
useContext消费跨层依赖memo 不屏蔽所消费 Context 的变化
useRefDOM 与非展示性可变值改 current 不会自动重渲染
useEffect外部系统同步清理与依赖完整,不能保证总在绘制后
useLayoutEffect绘制前测量与校正会阻塞绘制,避免滥用
useMemo / useCallback缓存计算 / 函数引用依赖错误会产生旧值,不作为正确性机制
memo基于 props 比较跳过部分渲染自身状态与 Context 仍能触发更新
useTransition非紧急更新与 pending不会自动取消网络请求
useDeferredValue延后某个值的消费不是固定延迟防抖
useId可访问性关联 ID不用作列表业务 key
useImperativeHandle控制对外 ref 句柄优先声明式数据流
useSyncExternalStore外部 store 一致订阅快照需满足稳定性约定

类生命周期与函数组件思路对照 ​

需求类组件常见位置函数组件思路
初始化状态constructor / state 字段useState 惰性初始化或 useReducer
根据输入计算 UIrender函数组件主体,保持纯净
订阅外部系统并随输入重连didMount + didUpdate一个 Effect 描述设置、依赖与清理
DOM 提交后、绘制前测量didMount/didUpdate 中相关逻辑useLayoutEffect
DOM 变更前保留快照getSnapshotBeforeUpdate没有可机械替换的单一 Hook,需重新设计读取时机
释放订阅componentWillUnmount对应 Effect 返回清理函数
捕获后代渲染错误getDerivedStateFromError / componentDidCatch仍可用类 Error Boundary 或封装库

不要把整个生命周期表理解为“一行对应一个 Hook”。Effect 是围绕一个外部同步过程组织,而非复刻类组件执行位置。

示例:请求依赖变化与清理 ​

jsx
import { useEffect, useState } from 'react';

function UserPanel({ userId }) {
  const [result, setResult] = useState({ status: 'loading' });

  useEffect(() => {
    const controller = new AbortController();
    let active = true;
    setResult({ status: 'loading' });

    async function load() {
      try {
        const response = await fetch(`/api/users/${encodeURIComponent(userId)}`, {
          signal: controller.signal,
        });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const data = await response.json();
        if (active) setResult({ status: 'success', data });
      } catch (error) {
        if (active) setResult({ status: 'error', message: String(error) });
      }
    }
    load();
    return () => {
      active = false;
      controller.abort();
    };
  }, [userId]);

  if (result.status === 'loading') return <p>加载中</p>;
  if (result.status === 'error') return <p role="alert">{result.message}</p>;
  return <p>{result.data.name}</p>;
}

讲解重点:Effect 跟随 userId 同步;清理同时使旧回调失效并尝试取消网络;所有请求结果统一按身份写入。这个简化示例不含缓存、SSR 数据预取和严格的“参数刚变化即清除旧显示”策略,真实路由页面可优先采用框架的数据加载层。

Fiber 概念图 ​

这是概念图,省略了挂起、错误恢复、更新队列重放和多 root 等路径。常见内部源码入口:Fiber、工作循环、Lane。链接指向会变化的 main 分支,阅读具体项目时应切换到对应 tag。

建议复习顺序 ​

  • 第一轮:1~20 题,能讲清状态、API、生命周期与副作用。
  • 第二轮:21~28 题,结合实际组件解释身份、并发与性能。
  • 第三轮:29~38 题,用 90 秒串起 Fiber,并能回答边界。

先尝试回答,再展开答案。每天进步一点。