react中原理
一、fiber
1、为什么需要fiber
React 采用「栈协调(Stack Reconciliation)」机制:
- 渲染 / 更新时,会递归遍历虚拟 DOM 树,同步执行整个 diff 过程,一旦开始就无法中断;
- 如果组件树很深、计算量大,会占用主线程(JS 执行)超过 16ms(浏览器刷新率 60fps,每帧≈16ms),导致页面卡顿(比如滚动、输入等交互无响应)
2、Fiber 到底是什么
- 数据结构层面:Fiber 是一个「虚拟节点(vnode)的增强版对象」,用来描述一个组件的 “工作单元”;
- 执行机制层面: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 做了兼容和增强),具体流程如下:
- 分配时间片:调度器(Scheduler)会给调和任务分配一个短时间片(比如 5ms),规定在这个时间内必须完成当前 Fiber 节点的遍历和 Diff。
- 检查剩余时间:每处理完一个 Fiber 节点,React 会调用
shouldYield()方法,判断当前时间片是否用尽,或者主线程是否有更高优先级任务(比如用户点击、输入)。 - 中断与恢复
- 中断:如果时间片用尽 / 有高优先级任务,就暂停当前调和流程,把控制权交还给浏览器,让浏览器处理渲染、交互等任务。
- 恢复:等浏览器主线程空闲时,调度器会重新唤醒暂停的调和任务,从上次中断的 Fiber 节点继续遍历,直到完成整个 Fiber 树的 Diff。
提交阶段的 Mutation 阶段和 Layout 阶段,一个是修改 DOM,一个是执行生命周期,为什么要把这两个步骤分开?
Mutation 阶段和 Layout 阶段,核心是区分「DOM 变更」和「基于变更后 DOM 的操作」,让流程逻辑更清晰、避免副作用冲突,具体好处有两点:
- 职责分离,规避时序问题Mutation 阶段只做 DOM 增删改,此时 DOM 已经更新,但浏览器还没完成重绘;而 Layout 阶段执行
componentDidMount/componentDidUpdate、useLayoutEffect这些钩子时,能拿到最新的 DOM 状态(比如节点宽高、滚动位置),可以直接基于更新后的 DOM 做计算或操作,不会拿到旧数据。 - 保证同步执行,避免视觉闪烁两个阶段都是同步执行的,DOM 变更和后续的布局相关操作会一次性完成,不会在中间插入浏览器的重绘过程,避免了因分步操作 DOM 导致的页面闪烁问题。
useLayoutEffect 和 useEffect 的执行时机,分别对应 Fiber 流程的哪个阶段?为什么 useEffect 不能在提交阶段同步执行
useLayoutEffect:对应 提交阶段的 Layout 子阶段,它在 Mutation 阶段 DOM 变更完成后同步执行,会阻塞浏览器的绘制,适合需要基于最新 DOM 做计算(比如获取节点尺寸、滚动位置)的场景。useEffect:不对应 Fiber 核心更新流程的任何阶段,它是在提交阶段完全结束、浏览器完成页面绘制后,在空闲时异步执行的。
而 useEffect 不能在提交阶段同步执行的核心原因有两点:
- 避免阻塞渲染:提交阶段本身是同步不可中断的,如果再加入
useEffect的回调执行,会延长主线程占用时间,导致动画卡顿、交互延迟等问题。 - 区分「副作用」的优先级:
useLayoutEffect负责处理和 DOM 强相关的、需要同步完成的副作用;useEffect负责处理网络请求、事件监听这类不紧急的异步逻辑,异步执行能平衡性能和功能需求。
我们用 useState 修改状态触发更新时,这个更新会被直接加入调度队列吗?优先级是怎么确定的当
当调用 useState 的更新函数时,不会直接加入调度队列,而是先创建一个包含更新数据和优先级的更新对象,挂载到当前组件对应的 Fiber 节点的更新队列中;之后 React 会触发一次调度请求,由调度器(Scheduler)统一处理 Fiber 节点的更新任务。
而更新任务的优先级确定规则,主要和触发更新的场景强相关:
- 高优先级:用户交互行为触发的更新(如点击、输入、滚动),优先级最高,会打断低优先级任务。
- 中优先级:组件自身状态变更(如
useState/useReducer同步更新),优先级中等。 - 低优先级:数据请求、定时器等异步操作触发的更新,优先级最低,会在浏览器空闲时执行。
另外,React 内部会通过 lane(车道模型)来标记不同优先级的更新,确保高优先级任务优先被处理。
React 优先级调度核心考点清单
-
更新触发流程
- 调用
useState/useReducer更新函数 → 创建更新对象(含数据、优先级)→ 挂载到对应 Fiber 节点的更新队列 → 触发调度请求。 - 不会直接加入调度队列,由 Scheduler 统一接管任务调度。
- 调用
-
优先级划分依据(按场景)
- 最高优先级:用户交互类(点击、输入、滚动、拖拽),需即时响应,可打断其他任务。
- 中优先级:组件同步状态变更(如父组件传参触发子组件更新)。
- 低优先级:异步操作类(网络请求回调、定时器、
useEffect内更新),浏览器空闲时执行。
-
核心实现机制:Lane 车道模型
- React 用
Lane替代旧版expirationTime,通过二进制位运算标记不同优先级的更新。 - 高优先级的 Lane 会优先占用资源,低优先级更新需等待高优先级任务执行完毕后恢复。
- 支持批量更新:同一事件循环内的多个更新会合并到相同 / 相近的 Lane 中,减少重复渲染。
- React 用
-
调度器(Scheduler)核心作用
- 基于
requestIdleCallback模拟实现,为任务分配时间片(约 5ms)。 - 提供
shouldYield()方法,判断当前任务是否需要中断,优先响应高优先级任务。 - 任务中断后,可基于 Fiber 节点的指针信息,在下一次空闲时恢复执行
- 基于
二、hooks原理
其核心原理是通过闭包和链表结构,在函数组件中持久化存储状态和逻辑
A. 闭包机制
"Hooks 利用 JavaScript 的闭包特性来'记住'状态。每个 Hook 函数都能访问到自己的状态值,即使组件重新渲染,这些值也能被保留。"
B. 链表存储
"React 内部为每个组件维护一个 Hook 链表。每次调用 Hook 时:
-
首次渲染:按调用顺序创建链表节点
-
后续渲染:按相同顺序遍历链表,获取对应的状态
这就解释了为什么 Hooks 必须在顶层调用,不能放在条件语句中"
"Hooks 深度集成在 React Fiber 架构中:
-
每个 Fiber 节点保存对应的 Hook 链表
-
更新时,React 会复用或重建 Hook 链表
-
这支持了并发模式下的状态一致性
一、React 核心原理深挖(基础进阶,必问)
- React 的虚拟 DOM和 Diff 算法原理?key 的作用是什么?为什么不能用索引当 key?
- React 的fiber 架构解决了什么问题?fiber 的核心工作机制(双缓冲、可中断渲染)是什么?
- React 的状态更新机制:同步更新和异步更新的区别?哪些场景下 setState 是同步的?
- 类组件的生命周期分为哪几个阶段?每个阶段的执行时机和常用场景?React 16 + 对生命周期做了哪些修改(为什么废弃 componentWillMount 等)?
- React 中props 和 state的区别?单向数据流的理解?props 能被修改吗?如何实现 props 的 “修改”?
- 什么是受控组件和非受控组件?分别适用于什么场景?如何在函数组件中操作非受控组件(ref)?
二、Hook 深度考察(React 核心,高频重点)
- React Hook 的设计初衷?解决了类组件的哪些问题?
- useState 的底层原理?为什么不能在条件 / 循环中使用 Hook?Hook 的执行规则是什么?
- useEffect 的依赖项数组怎么理解?空依赖、不传依赖、传具体值的区别?如何解决 useEffect 的闭包陷阱?
- useRef 的两个核心用途(保存 DOM 节点、保存跨渲染状态)?和 useState 的区别?
- useContext 的使用场景?如何解决 Context 重渲染的问题?
- useReducer 的使用场景?和 useState 的取舍?什么时候用 useReducer 更合适?
- 自定义 Hook 的定义规范和使用场景?如何抽离公共逻辑(比如请求、防抖、监听)?
- 常用的内置 Hook 拓展:useMemo 和 useCallback 的区别?分别解决什么性能问题?为什么不能滥用?
- React 18 新增的 Hook:useId、useTransition、useDeferredValue 分别是什么?适用于什么场景?
三、React 性能优化(项目实战,必问)
- React 项目中常见的性能瓶颈有哪些?分别怎么优化?
- 如何避免不必要的重渲染?(从组件、Hook、状态三个维度说)
- React.memo、useMemo、useCallback 的使用场景和区别?各自的适用边界?
- 列表渲染的性能优化方案?(key、虚拟列表、懒加载)
- 大体积组件的优化方式?(代码分割、懒加载 React.lazy+Suspense)
- 状态管理的性能优化?(比如局部状态代替全局状态、拆分 store)
- React 18 的并发渲染是什么?对性能优化有什么提升?
四、React 状态管理(项目必备,衔接 Vuex/Pinia)
- 为什么需要状态管理?React 中组件间通信的方式有哪些?(从父子、兄弟、跨层级说)
- Redux 的核心原理(三大原则、单向数据流)?Redux 的核心组成(store/action/reducer)分别做什么?
- Redux 和 Vuex/Pinia 的区别?(从设计思想、使用方式、响应式原理说)
- Redux Toolkit(RTK)是什么?为什么现在推荐用 RTK 代替原生 Redux?
- MobX 的核心原理(响应式)?和 Redux 的取舍?分别适用于什么项目?
- Recoil/Zustand/Jotai 这些轻量状态管理库的优势?适用于什么场景?
- 项目中如何选择状态管理方案?(比如小项目用 Context+useReducer,中大型用 RTK,轻量需求用 Zustand)
五、React 路由(React Router,衔接 Vue Router)
- React Router 6 和 5 的核心区别?(路由匹配、组件写法、导航方式)
- React Router 的核心组件 / API?(BrowserRouter/Routes/Route/Link/useNavigate/useParams)
- 如何实现路由懒加载?如何添加路由守卫(导航守卫)?
- 动态路由、嵌套路由的实现方式?和 Vue Router 的区别?
- 路由传参的方式有哪些?(params/query/state)各自的特点?
六、React 工程化 & 实战问题(校招 / 实习重点,考项目经验)
- React 项目的搭建方式?(Create React App/Vite/Umi)各自的优缺点?
- Vite 搭建 React 项目比 CRA 快的原因?(从构建原理说)
- React 中如何配置代理解决跨域问题?(开发环境 / 生产环境)
- React 中如何封装请求?(结合 axios,实现拦截器、请求取消、统一错误处理)
- 项目中如何处理跨组件的逻辑复用?(自定义 Hook / 高阶组件 HOC / 渲染属性 Render Props)
- 高阶组件(HOC)的原理和使用场景?和自定义 Hook 的区别?为什么现在更推荐自定义 Hook?
- React 中操作 DOM 的正确方式?为什么不建议直接操作 DOM?
- 项目中遇到的React 相关的坑?比如闭包陷阱、重渲染问题、Hook 使用错误,分别怎么解决的?
七、React 18 新特性(前沿考点,加分项)
- React 18 的核心升级是什么?(并发渲染 Concurrency)
- createRoot 代替 ReactDOM.render 的原因?
- 自动批处理(Automatic Batching)是什么?解决了什么问题?
- Suspense 的使用场景?(懒加载、数据请求)
- useTransition 和 useDeferredValue 的区别?如何实现 “非阻塞渲染”?
八、Vue 和 React 的对比(Vue 转 React 必问,考察技术理解)
- Vue 和 React 的核心设计思想区别?(渐进式 vs 组件化 + 函数式)
- 响应式原理的本质区别?(Vue2Object.defineProperty/Vue3Proxy vs React 状态更新触发重渲染)
- 组件写法、生命周期、状态管理的对比?
- 虚拟 DOM 和 Diff 算法的细微区别?
- 为什么选择从 Vue 转 React?(结合自身项目 / 学习说,避免踩 “踩一捧一” 的坑)
九、手写题(校招高频,考代码能力)
- 手写useState(简易版,理解闭包)
- 手写useEffect(简易版,理解依赖项检测)
- 手写自定义 Hook:比如 useRequest(请求 Hook)、useDebounce(防抖 Hook)、useEventListener(事件监听 Hook)
- 手写React.memo(简易版,理解浅比较)
- 手写发布订阅模式(理解 Redux / 事件通信的底层)
- 手写简易版 Redux(核心 store/action/reducer/dispatch)
- 组件封装:手写一个受控的 Input 组件、防抖的搜索组件、列表懒加载组件
更多推荐



所有评论(0)