一、fiber

1、为什么需要fiber

React 采用「栈协调(Stack Reconciliation)」机制:

  • 渲染 / 更新时,会递归遍历虚拟 DOM 树,同步执行整个 diff 过程,一旦开始就无法中断;
  • 如果组件树很深、计算量大,会占用主线程(JS 执行)超过 16ms(浏览器刷新率 60fps,每帧≈16ms),导致页面卡顿(比如滚动、输入等交互无响应)

2、Fiber 到底是什么

  1. 数据结构层面:Fiber 是一个「虚拟节点(vnode)的增强版对象」,用来描述一个组件的 “工作单元”;
  2. 执行机制层面:Fiber 是一套「可中断、可恢复、优先级驱动」的任务调度机制。
interface FiberNode {
  // 节点类型
  type: any;                    // 组件类型(函数组件、类组件、DOM 元素)
  key: string | null;           // key 属性
  props: any;                   // props

  // 树形结构
  child: FiberNode | null;      // 第一个子节点
  sibling: FiberNode | null;    // 下一个兄弟节点
  return: FiberNode | null;     // 父节点

  // 状态
  stateNode: any;               // 对应的 DOM 节点或组件实例
  memoizedState: any;           // 存储的状态(Hooks 的状态)
  memoizedProps: any;           // 存储的 props

  // 工作相关
  effectTag: number;            // 副作用标记(插入、更新、删除)
  updateQueue: any;             // 更新队列
  alternate: FiberNode | null;  // 指向另一个 Fiber(current 和 workInProgress)

  // Hooks 相关
  hooks: Hook | null;           // Hooks 链表
}

Fiber 树用 child(子)、sibling(兄弟)、return(父) 三个指针构建链表结构,实现 “可遍历、可中断”:

  • 遍历 Fiber 树时,先处理当前节点 → 再处理 child → child 处理完后,通过 return 回到父节点 → 再处理 sibling;
  • 每处理一个 Fiber 节点,就是一个 “小任务”,执行完后检查是否有更高优先级任务,若有则暂停,先处理高优先级任务。

 3. Fiber 的工作阶段

React 的渲染过程被 Fiber 拆分为 两大阶段,核心是 “阶段 1 可中断,阶段 2 不可中断”:

阶段 1:Reconciliation(协调 / 渲染阶段)
  • 核心工作:遍历 Fiber 树,执行 diff 算法,标记需要更新的 Fiber 节点(打 effectTag 标记,比如 “新增”“更新”“删除”);
  • 关键特性:可中断、可暂停、可恢复,优先级低的任务会被高优先级任务(比如点击、输入)打断;
  • 执行方式:通过 requestIdleCallback(浏览器空闲时执行)或 React 自研的调度器(Scheduler),每次只执行一个 Fiber 节点的小任务,执行完后检查主线程是否空闲,若不空闲则暂停,等下一次空闲时恢复。
阶段 2:Commit(提交阶段)
  • 核心工作:根据阶段 1 标记的 effectTag,执行实际的 DOM 操作(增删改),或执行组件的生命周期钩子(比如 componentDidMount/componentDidUpdate);
  • 关键特性:不可中断(DOM 操作必须一次性完成,否则会导致页面状态不一致);
  • 执行方式:一次性遍历 “副作用链表”(nextEffect),执行所有标记的操作,过程中不会被打断。

4、Fiber 与 Hooks 的关系

*Hooks 的状态存储在 Fiber 节点上:

* 每个 Hook 是链表中的一个节点:

* - Fiber.memoizedState 指向第一个 Hook

* - 每个 Hook 的 next 指向下一个 Hook

* - 通过遍历链表来访问所有 Hooks

* function Component() {

* const [state, setState] = useState(0); // Hook 1

* const [name, setName] = useState(''); // Hook 2

*

* // React 内部:

* // Fiber.memoizedState -> Hook1 -> Hook2 -> null

* // Hook1.memoizedState = 0

* // Hook2.memoizedState = ''

* }

*

5、调度机制:时间切片与优先级

1. 时间切片(Time Slicing)

•将渲染工作拆分为多个小“时间片”(约 5ms);

•每个时间片执行后让出主线程,让浏览器处理高优先级任务(交互/动画);

•空闲时继续执行下一个时间片,避免主线程长时间阻塞。

2. 优先级调度(从高到低)

优先级

适用场景

特点

Immediate

用户输入、点击事件

立即执行,可打断其他任务

UserBlocking

鼠标悬浮、拖拽

阻塞用户体验,优先执行

Normal

数据更新、网络请求回调

普通优先级,默认级别

Low

数据预取、非紧急更新

空闲时执行

Idle

后台清理、日志收集

极低优先级,最后执行

6、Fiber 树遍历规则

采用深度优先遍历(DFS),遍历顺序:

1.从根节点开始,先访问当前节点;

2.有子节点则优先访问第一个子节点;

3.无子节点则访问兄弟节点;

4.无兄弟节点则返回父节点,继续查找叔节点。

示例遍历顺序

Plain Text

* App

* / \

* A B

* / \ \

* C D E

*

* 遍历顺序:App -> A -> C -> D -> B -> E

7、Fiber 核心优势(对比旧 Stack Reconciler)

1.可中断性:渲染过程可暂停、恢复、丢弃,避免主线程阻塞;

2.优先级驱动:高优先级任务(如用户交互)可打断低优先级任务;

3.增量渲染:时间切片实现分片渲染,保持 UI 响应;

4.性能优化:双缓冲树复用节点,减少重复创建;

5.用户体验:避免长时间卡顿,提升交互流畅度。

8、实际应用场景

项目中组件更新的底层流程:

1.触发更新(setState/Props 变化)→ React 创建/更新 Fiber 节点;

2.执行组件函数 → 从 Fiber.memoizedState 读取 Hooks 状态;

3.生成新 JSX → 对比 current/workInProgress 树,标记差异;

4.Render 阶段完成后,Commit 阶段同步 DOM 更新;

5.切换双缓冲树,workInProgress 成为新的 current 树。

fiber更新流程图

Fiber 具体是怎么实现可中断和优先级调度的?

Fiber 架构的核心目标就是实现更新流程的可中断、可恢复、优先级调度,解决 React15 递归调和(Stack Reconciler)长期占用主线程,导致动画、交互卡顿的问题。

具体实现上,一是把递归遍历 Virtual DOM 的方式,改成了基于 Fiber 节点的链表遍历,每个 Fiber 节点保存了 child(子节点)、sibling(兄弟节点)、return(父节点)指针,能随时暂停遍历,下次从暂停的节点继续;二是引入了调度器(Scheduler),给不同更新任务分配优先级(比如用户输入是高优先级,数据请求是低优先级),利用 requestIdleCallback 模拟的空闲时间片执行任务,一旦主线程有高优先级任务进来,就暂停当前低优先级任务,优先处理高优先级任务。

React 是怎么判断什么时候该中断、什么时候该恢复的?这里面用到了什么核心 API?

是基于「时间片」和「优先级」的双重机制,核心依赖的 API 是 requestIdleCallback(React 做了兼容和增强),具体流程如下:

  1. 分配时间片:调度器(Scheduler)会给调和任务分配一个短时间片(比如 5ms),规定在这个时间内必须完成当前 Fiber 节点的遍历和 Diff。
  2. 检查剩余时间:每处理完一个 Fiber 节点,React 会调用 shouldYield() 方法,判断当前时间片是否用尽,或者主线程是否有更高优先级任务(比如用户点击、输入)。
  3. 中断与恢复
    • 中断:如果时间片用尽 / 有高优先级任务,就暂停当前调和流程,把控制权交还给浏览器,让浏览器处理渲染、交互等任务。
    • 恢复:等浏览器主线程空闲时,调度器会重新唤醒暂停的调和任务,从上次中断的 Fiber 节点继续遍历,直到完成整个 Fiber 树的 Diff。

提交阶段的 Mutation 阶段和 Layout 阶段,一个是修改 DOM,一个是执行生命周期,为什么要把这两个步骤分开?

Mutation 阶段和 Layout 阶段,核心是区分「DOM 变更」和「基于变更后 DOM 的操作」,让流程逻辑更清晰、避免副作用冲突,具体好处有两点:

  1. 职责分离,规避时序问题Mutation 阶段只做 DOM 增删改,此时 DOM 已经更新,但浏览器还没完成重绘;而 Layout 阶段执行 componentDidMount/componentDidUpdate、useLayoutEffect 这些钩子时,能拿到最新的 DOM 状态(比如节点宽高、滚动位置),可以直接基于更新后的 DOM 做计算或操作,不会拿到旧数据。
  2. 保证同步执行,避免视觉闪烁两个阶段都是同步执行的,DOM 变更和后续的布局相关操作会一次性完成,不会在中间插入浏览器的重绘过程,避免了因分步操作 DOM 导致的页面闪烁问题。

useLayoutEffect 和 useEffect 的执行时机,分别对应 Fiber 流程的哪个阶段?为什么 useEffect 不能在提交阶段同步执行

  1. useLayoutEffect:对应 提交阶段的 Layout 子阶段,它在 Mutation 阶段 DOM 变更完成后同步执行,会阻塞浏览器的绘制,适合需要基于最新 DOM 做计算(比如获取节点尺寸、滚动位置)的场景。
  2. useEffect:不对应 Fiber 核心更新流程的任何阶段,它是在提交阶段完全结束、浏览器完成页面绘制后,在空闲时异步执行的。

而 useEffect 不能在提交阶段同步执行的核心原因有两点:

  • 避免阻塞渲染:提交阶段本身是同步不可中断的,如果再加入 useEffect 的回调执行,会延长主线程占用时间,导致动画卡顿、交互延迟等问题。
  • 区分「副作用」的优先级:useLayoutEffect 负责处理和 DOM 强相关的、需要同步完成的副作用;useEffect 负责处理网络请求、事件监听这类不紧急的异步逻辑,异步执行能平衡性能和功能需求。

我们用 useState 修改状态触发更新时,这个更新会被直接加入调度队列吗?优先级是怎么确定的当

当调用 useState 的更新函数时,不会直接加入调度队列,而是先创建一个包含更新数据和优先级的更新对象,挂载到当前组件对应的 Fiber 节点的更新队列中;之后 React 会触发一次调度请求,由调度器(Scheduler)统一处理 Fiber 节点的更新任务。

而更新任务的优先级确定规则,主要和触发更新的场景强相关:

  1. 高优先级:用户交互行为触发的更新(如点击、输入、滚动),优先级最高,会打断低优先级任务。
  2. 中优先级:组件自身状态变更(如 useState/useReducer 同步更新),优先级中等。
  3. 低优先级:数据请求、定时器等异步操作触发的更新,优先级最低,会在浏览器空闲时执行。

另外,React 内部会通过 lane(车道模型)来标记不同优先级的更新,确保高优先级任务优先被处理。

React 优先级调度核心考点清单

  1. 更新触发流程

    • 调用 useState/useReducer 更新函数 → 创建更新对象(含数据、优先级)→ 挂载到对应 Fiber 节点的更新队列 → 触发调度请求。
    • 不会直接加入调度队列,由 Scheduler 统一接管任务调度。
  2. 优先级划分依据(按场景)

    • 最高优先级:用户交互类(点击、输入、滚动、拖拽),需即时响应,可打断其他任务。
    • 中优先级:组件同步状态变更(如父组件传参触发子组件更新)。
    • 低优先级:异步操作类(网络请求回调、定时器、useEffect 内更新),浏览器空闲时执行。
  3. 核心实现机制:Lane 车道模型

    • React 用 Lane 替代旧版 expirationTime,通过二进制位运算标记不同优先级的更新。
    • 高优先级的 Lane 会优先占用资源,低优先级更新需等待高优先级任务执行完毕后恢复。
    • 支持批量更新:同一事件循环内的多个更新会合并到相同 / 相近的 Lane 中,减少重复渲染。
  4. 调度器(Scheduler)核心作用

    • 基于 requestIdleCallback 模拟实现,为任务分配时间片(约 5ms)。
    • 提供 shouldYield() 方法,判断当前任务是否需要中断,优先响应高优先级任务。
    • 任务中断后,可基于 Fiber 节点的指针信息,在下一次空闲时恢复执行

二、hooks原理

其核心原理是通过闭包和链表结构,在函数组件中持久化存储状态和逻辑

A. 闭包机制

"Hooks 利用 JavaScript 的闭包特性来'记住'状态。每个 Hook 函数都能访问到自己的状态值,即使组件重新渲染,这些值也能被保留。"

B. 链表存储

"React 内部为每个组件维护一个 Hook 链表。每次调用 Hook 时:

  • 首次渲染:按调用顺序创建链表节点

  • 后续渲染:按相同顺序遍历链表,获取对应的状态
    这就解释了为什么 Hooks 必须在顶层调用,不能放在条件语句中"

"Hooks 深度集成在 React Fiber 架构中:

  • 每个 Fiber 节点保存对应的 Hook 链表

  • 更新时,React 会复用或重建 Hook 链表

  • 这支持了并发模式下的状态一致性

一、React 核心原理深挖(基础进阶,必问)

  1. React 的虚拟 DOM和 Diff 算法原理?key 的作用是什么?为什么不能用索引当 key?
  2. React 的fiber 架构解决了什么问题?fiber 的核心工作机制(双缓冲、可中断渲染)是什么?
  3. React 的状态更新机制:同步更新和异步更新的区别?哪些场景下 setState 是同步的?
  4. 类组件的生命周期分为哪几个阶段?每个阶段的执行时机和常用场景?React 16 + 对生命周期做了哪些修改(为什么废弃 componentWillMount 等)?
  5. React 中props 和 state的区别?单向数据流的理解?props 能被修改吗?如何实现 props 的 “修改”?
  6. 什么是受控组件和非受控组件?分别适用于什么场景?如何在函数组件中操作非受控组件(ref)?

二、Hook 深度考察(React 核心,高频重点)

  1. React Hook 的设计初衷?解决了类组件的哪些问题?
  2. useState 的底层原理?为什么不能在条件 / 循环中使用 Hook?Hook 的执行规则是什么?
  3. useEffect 的依赖项数组怎么理解?空依赖、不传依赖、传具体值的区别?如何解决 useEffect 的闭包陷阱?
  4. useRef 的两个核心用途(保存 DOM 节点、保存跨渲染状态)?和 useState 的区别?
  5. useContext 的使用场景?如何解决 Context 重渲染的问题?
  6. useReducer 的使用场景?和 useState 的取舍?什么时候用 useReducer 更合适?
  7. 自定义 Hook 的定义规范和使用场景?如何抽离公共逻辑(比如请求、防抖、监听)?
  8. 常用的内置 Hook 拓展:useMemo 和 useCallback 的区别?分别解决什么性能问题?为什么不能滥用?
  9. React 18 新增的 Hook:useId、useTransition、useDeferredValue 分别是什么?适用于什么场景?

三、React 性能优化(项目实战,必问)

  1. React 项目中常见的性能瓶颈有哪些?分别怎么优化?
  2. 如何避免不必要的重渲染?(从组件、Hook、状态三个维度说)
  3. React.memo、useMemo、useCallback 的使用场景和区别?各自的适用边界?
  4. 列表渲染的性能优化方案?(key、虚拟列表、懒加载)
  5. 大体积组件的优化方式?(代码分割、懒加载 React.lazy+Suspense)
  6. 状态管理的性能优化?(比如局部状态代替全局状态、拆分 store)
  7. React 18 的并发渲染是什么?对性能优化有什么提升?

四、React 状态管理(项目必备,衔接 Vuex/Pinia)

  1. 为什么需要状态管理?React 中组件间通信的方式有哪些?(从父子、兄弟、跨层级说)
  2. Redux 的核心原理(三大原则、单向数据流)?Redux 的核心组成(store/action/reducer)分别做什么?
  3. Redux 和 Vuex/Pinia 的区别?(从设计思想、使用方式、响应式原理说)
  4. Redux Toolkit(RTK)是什么?为什么现在推荐用 RTK 代替原生 Redux?
  5. MobX 的核心原理(响应式)?和 Redux 的取舍?分别适用于什么项目?
  6. Recoil/Zustand/Jotai 这些轻量状态管理库的优势?适用于什么场景?
  7. 项目中如何选择状态管理方案?(比如小项目用 Context+useReducer,中大型用 RTK,轻量需求用 Zustand)

五、React 路由(React Router,衔接 Vue Router)

  1. React Router 6 和 5 的核心区别?(路由匹配、组件写法、导航方式)
  2. React Router 的核心组件 / API?(BrowserRouter/Routes/Route/Link/useNavigate/useParams)
  3. 如何实现路由懒加载?如何添加路由守卫(导航守卫)?
  4. 动态路由、嵌套路由的实现方式?和 Vue Router 的区别?
  5. 路由传参的方式有哪些?(params/query/state)各自的特点?

六、React 工程化 & 实战问题(校招 / 实习重点,考项目经验)

  1. React 项目的搭建方式?(Create React App/Vite/Umi)各自的优缺点?
  2. Vite 搭建 React 项目比 CRA 快的原因?(从构建原理说)
  3. React 中如何配置代理解决跨域问题?(开发环境 / 生产环境)
  4. React 中如何封装请求?(结合 axios,实现拦截器、请求取消、统一错误处理)
  5. 项目中如何处理跨组件的逻辑复用?(自定义 Hook / 高阶组件 HOC / 渲染属性 Render Props)
  6. 高阶组件(HOC)的原理和使用场景?和自定义 Hook 的区别?为什么现在更推荐自定义 Hook?
  7. React 中操作 DOM 的正确方式?为什么不建议直接操作 DOM?
  8. 项目中遇到的React 相关的坑?比如闭包陷阱、重渲染问题、Hook 使用错误,分别怎么解决的?

七、React 18 新特性(前沿考点,加分项)

  1. React 18 的核心升级是什么?(并发渲染 Concurrency)
  2. createRoot 代替 ReactDOM.render 的原因?
  3. 自动批处理(Automatic Batching)是什么?解决了什么问题?
  4. Suspense 的使用场景?(懒加载、数据请求)
  5. useTransition 和 useDeferredValue 的区别?如何实现 “非阻塞渲染”?

八、Vue 和 React 的对比(Vue 转 React 必问,考察技术理解)

  1. Vue 和 React 的核心设计思想区别?(渐进式 vs 组件化 + 函数式)
  2. 响应式原理的本质区别?(Vue2Object.defineProperty/Vue3Proxy vs React 状态更新触发重渲染)
  3. 组件写法、生命周期、状态管理的对比?
  4. 虚拟 DOM 和 Diff 算法的细微区别?
  5. 为什么选择从 Vue 转 React?(结合自身项目 / 学习说,避免踩 “踩一捧一” 的坑)

九、手写题(校招高频,考代码能力)

  1. 手写useState(简易版,理解闭包)
  2. 手写useEffect(简易版,理解依赖项检测)
  3. 手写自定义 Hook:比如 useRequest(请求 Hook)、useDebounce(防抖 Hook)、useEventListener(事件监听 Hook)
  4. 手写React.memo(简易版,理解浅比较)
  5. 手写发布订阅模式(理解 Redux / 事件通信的底层)
  6. 手写简易版 Redux(核心 store/action/reducer/dispatch)
  7. 组件封装:手写一个受控的 Input 组件、防抖的搜索组件、列表懒加载组件

更多推荐