一、Fiber架构:React渲染的核心基石
1.1 为什么需要Fiber?
React 15及之前采用“栈协调器”(Stack Reconciler),渲染过程是同步且不可中断的:一旦开始调和(Reconciliation),会持续占用主线程,若组件树庞大,会导致UI卡顿(如滚动、输入等高频交互无响应)。
Fiber架构是React 16推出的“增量协调器”(Incremental Reconciler),核心目标是将同步渲染拆分为可中断、可恢复、优先级可控的小任务,实现主线程空闲时执行渲染任务,有高优先级任务(如用户输入)时暂停渲染,释放主线程。
1.2 Fiber核心设计:数据结构与工作循环
1.2.1 核心数据结构:Fiber节点
Fiber本质是“工作单元”,每个DOM元素/组件对应一个Fiber节点,通过链表结构串联,替代传统栈结构,支持中断后恢复。核心属性如下(简化版):
const FiberNode = {
tag: 0, // 节点类型(元素/函数组件/类组件等)
key: null, // 用于Diff算法复用节点
type: null, // 组件类型或DOM标签名
return: null, // 父Fiber节点(链表回溯指针)
child: null, // 第一个子Fiber节点
sibling: null, // 下一个兄弟Fiber节点
index: 0, // 子节点在父节点中的索引
pendingProps: null, // 待应用的props
memoizedProps: null, // 已缓存的props(当前渲染用)
memoizedState: null, // 已缓存的状态(函数组件存Hooks链表,类组件存实例状态)
alternate: null, // 双缓存Fiber树的对应节点
flags: 0, // 节点标记(更新/删除/新增等)
nextEffect: null, // 副作用链表指针
};核心链表关系:父节点通过child指向第一个子节点,子节点通过sibling指向兄弟节点,所有节点通过return指向父节点,形成完整的Fiber树遍历链路(深度优先遍历的非递归实现)。
1.2.2 双缓存Fiber树
React通过“双缓存”机制避免DOM操作冲突,提升渲染性能,存在两棵Fiber树:
Current树:当前渲染在页面上的Fiber树,与真实DOM一一对应。
WorkInProgress树:正在调和(Reconciliation)的Fiber树,基于Current树拷贝生成,所有更新操作在这棵树上进行,避免直接修改Current树导致UI不稳定。
切换逻辑:当WorkInProgress树调和完成后,通过root.current = workInProgress替换Current树,再批量执行DOM操作(提交阶段),实现无感知更新。
1.2.3 Fiber工作循环(两大阶段)
Fiber工作循环由react-reconciler包主导,分为“调度阶段(Reconciliation)”和“提交阶段(Commit)”,仅调度阶段可中断。
调度阶段(可中断、可恢复):
核心任务:遍历WorkInProgress树,执行Diff算法、更新Fiber节点状态、收集副作用(如DOM增删改、Hooks副作用)。
流程:从根Fiber开始,通过“child→sibling→return”链表遍历所有节点,对每个节点执行“beginWork”(处理节点更新,生成子节点)和“completeWork”(完成节点处理,收集副作用)。
中断机制:依赖
react-scheduler的时间切片(Time Slicing),每执行一个工作单元后,检查剩余时间(默认16ms,匹配屏幕刷新率),若超时或有高优先级任务,暂停遍历,记录当前进度(通过链表指针),待主线程空闲后恢复。
提交阶段(不可中断):
核心任务:执行调度阶段收集的副作用,更新真实DOM,触发组件生命周期(类组件)或Hooks回调(函数组件)。
流程:分为“before mutation”(执行DOM操作前,触发getSnapshotBeforeUpdate)、“mutation”(执行DOM增删改)、“layout”(DOM更新后,触发componentDidMount/Update、useLayoutEffect回调)三个子阶段。
不可中断原因:DOM操作是同步的,中断会导致UI撕裂、状态不一致。
1.3 核心包协同:Fiber的实现依赖
react-reconciler:实现Fiber树的调和逻辑(beginWork、completeWork)、Diff算法、副作用收集。react-scheduler:提供时间切片、优先级调度能力,决定Fiber工作单元何时执行、何时中断。react-dom:提供宿主环境适配(浏览器DOM),在提交阶段执行真实DOM操作,触发生命周期/Hooks回调。
1.4 高频面试题
Q:Fiber架构解决了React 15的什么问题?核心设计思路是什么? A:解决了同步渲染导致的主线程阻塞问题。核心思路:用链表结构替代栈结构,将渲染任务拆分为可中断的工作单元,通过双缓存机制和时间切片,实现渲染与高优先级任务的并发调度。
Q:Fiber工作循环的两大阶段是什么?各自特点和任务是什么? A:调度阶段(可中断):遍历Fiber树、Diff更新、收集副作用;提交阶段(不可中断):执行副作用、更新DOM、触发回调。
Q:双缓存Fiber树的作用是什么?切换逻辑是什么? A:避免DOM操作冲突,提升渲染稳定性。切换逻辑:WorkInProgress树调和完成后,通过root.current指向WorkInProgress树,替换Current树。
二、并发模式(Concurrent Mode)
2.1 核心定义
并发模式不是具体API,而是React的一种渲染策略,基于Fiber架构实现,允许React“同时”处理多个任务(实际是通过时间切片和优先级调度实现的伪并发),核心特性:可中断、可恢复、优先级驱动。
注意:React 18中已用“并发特性”(如useTransition、Suspense)替代旧的Concurrent Mode标志,不再需要手动开启模式,而是通过API按需启用并发能力。
2.2 优先级调度机制(react-scheduler核心能力)
并发模式的核心是“优先级”,react-scheduler将任务分为不同优先级,高优先级任务中断低优先级任务,确保用户交互(如点击、输入)优先响应。优先级从高到低:
ImmediatePriority:立即执行(同步),如flushSync触发的更新。
UserBlockingPriority:用户阻塞级(250ms超时),如点击、输入、滚动等用户交互。
NormalPriority:普通级(5000ms超时),如普通状态更新。
LowPriority:低优先级(10000ms超时),如列表滚动时的非关键更新。
IdlePriority:空闲级,仅在主线程完全空闲时执行,如日志上报。
实现原理:react-scheduler通过requestIdleCallback(降级为setTimeout)实现时间切片,每轮时间切片结束后,检查是否有更高优先级任务,若有则切换任务执行。
2.3 核心并发API(React 18+)
useTransition:将低优先级更新标记为“过渡更新”,不阻塞高优先级任务(如输入框输入时,列表筛选可作为过渡更新)。Suspense:配合React.lazy实现组件懒加载,或等待数据请求完成后渲染,支持并发渲染中的“暂停-恢复”。useDeferredValue:延迟更新非关键值,确保高优先级任务优先完成。
2.4 高频面试题
Q:React并发模式的核心特性是什么?与同步模式的区别? A:核心特性:可中断、可恢复、优先级驱动。区别:同步模式渲染不可中断,阻塞主线程;并发模式通过时间切片和优先级调度,实现渲染与高优先级任务的协同,避免卡顿。
Q:useTransition的作用和底层原理? A:作用:标记低优先级更新,不阻塞高优先级任务。原理:将更新拆分为“紧急更新”(如输入)和“过渡更新”,过渡更新被标记为低优先级,若有高优先级任务则中断,空闲后恢复。
Q:react-scheduler如何实现优先级调度? A:通过划分任务优先级,结合时间切片机制,每轮时间切片后检查优先级,高优先级任务中断低优先级任务,确保关键任务优先执行。
三、可中断渲染
3.1 核心原理
可中断渲染是Fiber和并发模式的核心能力,本质是“将渲染任务拆分为细粒度工作单元,通过时间切片和优先级调度,实现任务的暂停、中断与恢复”。
关键支撑:
链表结构:Fiber节点的return/child/sibling指针,使遍历可随时中断,下次恢复时能通过指针找到上一次的位置。
状态缓存:WorkInProgress树缓存当前渲染状态,中断后无需重新计算,恢复时直接基于缓存继续。
时间切片:react-scheduler控制每轮工作单元执行时间(≤16ms),超时则暂停,释放主线程。
3.2 中断与恢复流程
React调度器从任务队列取出高优先级任务,开始执行Fiber调和(调度阶段)。
每执行一个Fiber节点的beginWork/completeWork(一个工作单元),检查剩余时间:
若有剩余时间,继续遍历下一个Fiber节点。
若超时或有更高优先级任务,记录当前遍历位置(当前Fiber节点),暂停调和,将控制权交还给主线程。
主线程执行完高优先级任务后,空闲时调度器恢复调和,从上次暂停的Fiber节点继续遍历。
调和完成后进入提交阶段,执行DOM更新(不可中断)。
3.3 注意事项:副作用与可中断性
调度阶段(可中断)不能执行有副作用的操作(如DOM修改、网络请求),否则中断后会导致副作用重复执行或遗漏。因此:
调度阶段仅收集副作用(标记在Fiber节点的flags中),不执行。
副作用统一在提交阶段(不可中断)执行,确保执行顺序和完整性。
3.4 高频面试题
Q:React可中断渲染的实现基础是什么? A:基于Fiber链表结构(可恢复遍历)、WorkInProgress树(状态缓存)、react-scheduler时间切片(控制执行时长)三大核心。
Q:为什么调度阶段可中断,提交阶段不可中断? A:调度阶段仅处理Fiber节点的计算和状态更新,无真实DOM操作,中断无副作用;提交阶段执行DOM操作和副作用回调,DOM操作同步且不可中断,否则会导致UI不一致。
四、Hooks底层原理
4.1 核心设计目标
Hooks解决函数组件无状态、无生命周期、状态逻辑复用复杂(如高阶组件嵌套)的问题,使函数组件能拥有状态管理、副作用处理能力,同时简化逻辑复用。
4.2 底层数据结构:Hooks链表
函数组件的Hooks存储在对应Fiber节点的memoizedState中,以单向链表形式组织,每个Hook对应一个链表节点(Hook对象)。核心结构:
const Hook = {
memoizedState: null, // Hook缓存的状态(如useState的state)
baseState: null, // 基础状态(用于处理更新队列)
baseQueue: null, // 更新队列(如useState的setState回调队列)
queue: null, // 待处理的更新队列
next: null, // 下一个Hook节点(链表指针)
};关联逻辑:
函数组件渲染时,会从Fiber节点的memoizedState取出Hooks链表,通过“指针遍历”依次处理每个Hook。
每次调用useState、useEffect等Hooks API,本质是创建/复用Hook节点,更新链表状态。
4.3 核心Hooks实现细节
4.3.1 useState
初始化阶段:
调用useState时,创建Hook节点,将初始值作为memoizedState,初始化空更新队列(queue)。
将Hook节点加入Hooks链表,返回[memoizedState, dispatchSetState](dispatch是更新触发函数)。
更新阶段:
调用dispatchSetState时,将更新操作(新值或更新函数)加入Hook的更新队列。
React调度器触发重新渲染,遍历Hooks链表时,处理该Hook的更新队列:合并更新、计算新状态,更新Hook的memoizedState。
函数组件重新执行,返回新状态。
批量更新:同一事件循环中多次调用dispatchSetState,React会合并更新队列,仅执行一次渲染(由react-dom的批量更新机制实现)。
4.3.2 useEffect
初始化阶段:
调用useEffect时,创建Hook节点,缓存回调函数、依赖数组。
将副作用信息(回调、依赖、清理函数)标记在Fiber节点的flags中,等待提交阶段执行。
更新阶段:
重新执行useEffect,对比新旧依赖数组(浅比较)。
若依赖变化:先执行上一轮的清理函数(提交阶段的layout之前),再缓存新回调和依赖,标记新副作用。
若依赖不变:复用旧副作用,不执行回调和清理。
执行时机:useEffect回调在DOM更新后执行(提交阶段的layout之后),且是异步的,不阻塞DOM渲染;清理函数在组件卸载或依赖变化时执行。
4.3.3 useRef
useRef返回一个不变的ref对象({ current: ... }),底层实现:
初始化时,Hook节点的memoizedState存储ref对象。
更新阶段,复用该Hook节点,ref对象引用不变,仅current属性可修改。
特性:ref变化不会触发组件重新渲染,可用于存储DOM元素、跨渲染周期的变量。
4.4 Hooks使用限制的底层原因
Hooks不能在条件语句、循环、嵌套函数中调用,核心原因:Hooks链表的遍历依赖调用顺序,顺序错乱会导致链表匹配错误,状态丢失或错乱。
示例:
// 错误示例
if (condition) {
useState(0); // 条件语句导致调用顺序不稳定
}
useEffect(() => {}, []);
// 若condition为false,Hooks链表遍历到useEffect时,会匹配到错误的Hook节点(原本属于useState的节点),导致状态异常。4.5 高频面试题
Q:Hooks的底层数据结构是什么?为什么Hooks不能在条件语句中调用? A:底层是单向链表,存储在Fiber节点的memoizedState中。原因:Hooks依赖调用顺序遍历链表,顺序错乱会导致链表匹配错误,状态异常。
Q:useState的更新队列是如何工作的?批量更新的原理? A:useState的更新会加入Hook节点的更新队列,重新渲染时合并队列计算新状态。批量更新:react-dom在事件回调、生命周期中合并同一事件循环的更新,仅执行一次渲染,flushSync可打破批量更新。
Q:useEffect和useLayoutEffect的区别?执行时机分别是什么? A:useEffect异步执行,在DOM更新后(提交阶段layout之后),不阻塞渲染;useLayoutEffect同步执行,在DOM更新后、浏览器绘制前(提交阶段layout阶段),阻塞渲染。适合需要操作DOM后立即获取布局信息的场景。
Q:useRef的ref对象为什么能跨渲染周期保持不变? A:useRef的ref对象存储在Hook节点的memoizedState中,更新阶段复用Hook节点,ref对象的引用不变,仅current属性可修改,且ref变化不触发重新渲染。
五、重新渲染内部流程
5.1 重新渲染的触发条件
状态更新:函数组件useState/useReducer触发,类组件setState触发。
Props变化:父组件重新渲染,子组件props发生浅变化(若子组件未用memo包裹,即使props不变也会重新渲染)。
强制更新:调用forceUpdate(类组件),或通过useState更新一个无关状态(函数组件)。
上下文变化:组件使用的Context.Provider值变化,会触发所有消费该Context的组件重新渲染。
5.2 完整重新渲染流程(基于Fiber架构)
以函数组件useState更新为例,流程如下(涉及react、react-reconciler、react-scheduler、react-dom协同):
触发更新:调用useState返回的dispatchSetState,将更新操作加入对应Hook的更新队列,调用react-reconciler的scheduleUpdateOnFiber,触发调度。
任务调度:react-scheduler根据更新优先级,将任务加入调度队列,等待主线程空闲(时间切片)。
调度阶段(Reconciliation):
react-reconciler创建WorkInProgress树(基于Current树拷贝),从根Fiber开始遍历,执行beginWork。
遍历到触发更新的组件Fiber节点,处理Hook更新队列,计算新状态,更新Hook的memoizedState。
执行Diff算法,对比新旧虚拟DOM,标记Fiber节点的更新类型(新增/删除/修改),收集副作用。
若有高优先级任务或时间切片超时,中断遍历,待空闲后恢复;否则继续遍历至所有节点处理完成。
提交阶段(Commit):
before mutation阶段:执行getSnapshotBeforeUpdate(类组件),清理上一轮useEffect副作用。
mutation阶段:react-dom执行DOM操作(根据Fiber节点的副作用标记),更新真实DOM。
layout阶段:更新Current树(root.current = workInProgress),触发componentDidMount/Update(类组件)、useLayoutEffect回调,执行useEffect回调(异步)。
完成渲染:浏览器绘制更新后的DOM,组件呈现新状态。
5.3 批量更新机制
React默认在“合成事件回调”“生命周期函数”“useEffect回调”中开启批量更新,即多次状态更新合并为一次渲染,减少DOM操作次数,提升性能。
实现原理:react-dom维护一个“更新批次”标志,在上述场景中,将更新加入队列,待回调执行完成后,统一触发调度和渲染;若用flushSync包裹更新,会强制立即执行更新,打破批量更新。
5.4 高频面试题
Q:React组件重新渲染的完整流程是什么?涉及哪些核心包? A:流程:触发更新→任务调度→调度阶段(调和Fiber树、Diff、收集副作用)→提交阶段(执行副作用、更新DOM、触发回调)。涉及包:react(API定义)、react-reconciler(调和)、react-scheduler(调度)、react-dom(DOM操作)。
Q:父组件重新渲染,子组件一定会重新渲染吗?为什么? A:不一定。若子组件用memo包裹,且props浅比较不变,则子组件不会重新渲染;若未用memo,即使props不变,子组件也会跟随父组件重新渲染(React默认行为)。
Q:如何打破React的批量更新? A:用flushSync包裹状态更新,强制立即执行更新;或在异步回调(如setTimeout、Promise.then)中触发更新,React 18前异步回调中不开启批量更新(React 18后通过createRoot开启全局批量更新,异步回调也支持)。
六、Hooks的意义
6.1 解决函数组件的局限性
React早期函数组件是“无状态组件”,仅能接收props渲染UI,Hooks使函数组件具备:
状态管理能力(useState、useReducer)。
副作用处理能力(useEffect、useLayoutEffect)。
生命周期等价能力(useEffect模拟componentDidMount/Update/Unmount)。
DOM操作能力(useRef)。
6.2 简化状态逻辑复用
传统状态逻辑复用依赖高阶组件(HOC)、Render Props,但会导致“组件嵌套地狱”,代码可读性差。Hooks通过“自定义Hooks”实现逻辑复用,无需修改组件结构,代码更简洁。
示例:自定义useRequest Hooks封装请求逻辑,多个组件可直接调用,复用请求状态(加载中、成功、失败)和回调。
6.3 统一组件模型,降低学习成本
Hooks使函数组件成为React的主流组件模型,替代类组件的复杂生命周期、this绑定、状态管理逻辑,减少“类组件与函数组件并存”的心智负担,新开发者可快速上手。
6.4 更好地支持并发模式
函数组件更适合并发模式的可中断渲染:无类组件的this状态依赖,Hooks链表的状态存储更易被中断和恢复,配合useTransition等并发API,能更好地实现高性能交互。
6.5 高频面试题
Q:Hooks相比类组件有哪些优势? A:① 简化状态逻辑复用(自定义Hooks替代HOC/Render Props);② 消除类组件的this绑定、生命周期嵌套问题;③ 函数组件更简洁,学习成本低;④ 更好地支持并发模式。
Q:自定义Hooks的核心原则是什么?如何实现一个自定义Hooks? A:核心原则:① 命名以use开头;② 内部可调用其他Hooks;③ 仅用于逻辑复用,不返回JSX。实现:封装通用逻辑(如请求、防抖),通过Hooks管理状态和副作用,暴露结果和操作函数。
七、性能优化
7.1 组件级优化
7.1.1 React.memo(函数组件)/PureComponent(类组件)
作用:浅比较props,若props未变化,阻止组件重新渲染。
实现原理:memo接收组件和自定义比较函数(可选),返回一个包装组件,重新渲染前对比新旧props,浅比较相等则跳过调和阶段。
注意:若props包含引用类型(对象、数组),浅比较可能失效,需配合useMemo/useCallback优化。
7.1.2 useMemo(缓存计算结果)
作用:缓存耗时计算的结果,依赖不变时复用结果,避免每次渲染重复计算。
原理:将计算逻辑和依赖数组传入useMemo,重新渲染时若依赖不变,返回缓存结果;依赖变化则重新计算并缓存。
注意:仅用于耗时计算,普通变量无需缓存,避免额外性能开销。
7.1.3 useCallback(缓存函数引用)
作用:缓存函数引用,避免每次渲染生成新函数,导致子组件(接收该函数作为props)不必要的重新渲染。
原理:与useMemo类似,依赖不变时复用函数引用,配合memo使用,确保子组件props浅比较不变。
7.2 渲染流程优化
7.2.1 避免不必要的状态更新
setState时对比新旧状态,相同则不触发更新(类组件需手动判断,函数组件useState会自动忽略相同值)。
拆分状态:将不相关的状态拆分为多个useState,避免一个状态更新导致无关逻辑重新执行。
7.2.2 合理使用并发API
用useTransition将低优先级更新(如列表筛选、数据加载)标记为过渡更新,避免阻塞高优先级任务(输入、点击),提升交互流畅度。
7.2.3 虚拟列表(长列表优化)
对于万级以上数据的长列表,直接渲染所有节点会导致Fiber树过大、调和耗时,需用虚拟列表(如react-window、react-virtualized):仅渲染当前可视区域的节点,滚动时动态复用节点,减少DOM数量和调和成本。
7.3 副作用优化
7.3.1 精准控制useEffect依赖
useEffect依赖数组需精准配置,避免依赖冗余(导致不必要的副作用执行)或缺失(导致闭包陷阱,获取旧状态)。
闭包陷阱解决:用useRef存储最新状态,或拆分useEffect为多个,分别控制依赖。
7.3.2 清理副作用
在useEffect回调中返回清理函数,清除定时器、事件监听、网络请求等,避免内存泄漏(如组件卸载前取消未完成的请求)。
7.4 其他优化手段
代码分割:用React.lazy和Suspense实现组件懒加载,减少首屏加载体积,提升首屏渲染速度。
Context优化:拆分Context为多个细粒度Context,避免一个Context变化导致所有消费组件重新渲染。
避免使用index作为key:列表渲染时,用唯一ID作为key,确保Diff算法正确复用节点,避免不必要的DOM更新。
7.5 高频面试题
Q:useMemo和useCallback的区别和使用场景? A:useMemo缓存计算结果,用于耗时计算场景;useCallback缓存函数引用,用于避免子组件因函数props变化而不必要渲染的场景。两者均依赖数组控制缓存失效。
Q:React.memo的局限性是什么?如何解决? A:局限性:仅浅比较props,若props包含引用类型,浅比较可能失效。解决:配合useMemo/useCallback缓存引用类型props,确保新旧props浅比较相等。
Q:长列表优化的核心思路是什么?常用方案有哪些? A:核心思路:减少渲染的节点数量,避免Fiber调和和DOM操作耗时。方案:虚拟列表(react-window)、分页加载、按需渲染。
Q:如何排查React组件的不必要渲染? A:① 用React DevTools的Highlight Updates功能,标记重新渲染的组件;② 在组件中打印日志,判断是否触发不必要渲染;③ 检查props是否变化、状态更新是否必要,针对性用memo/useMemo/useCallback优化。
八、虚拟DOM原理
8.1 核心定义与价值
虚拟DOM(Virtual DOM)是用JS对象模拟真实DOM的结构和属性,作为React的“中间层”,核心价值:
跨平台适配:虚拟DOM与宿主环境无关,react-dom(浏览器)、react-native(移动端)可基于虚拟DOM生成对应平台的真实节点。
减少DOM操作:通过Diff算法对比新旧虚拟DOM,仅更新变化的部分,避免全量DOM更新(DOM操作是昂贵的)。
抽象渲染逻辑:将DOM操作抽象为虚拟DOM的更新,简化组件渲染逻辑。
8.2 虚拟DOM的结构(React Element)
React.createElement创建的React Element就是虚拟DOM的核心结构,简化版如下:
const ReactElement = {
$$typeof: Symbol.for('react.element'), // 标记为React元素
type: 'div', // 元素类型(DOM标签名/组件)
key: null, // 用于Diff复用
ref: null, // 用于获取DOM元素
props: { children: [], className: '' }, // 元素属性和子元素
_owner: null, // 所属组件
};函数组件返回的JSX会被Babel编译为React.createElement调用,生成虚拟DOM树。
8.3 Diff算法:虚拟DOM的核心
Diff算法是虚拟DOM的核心,目标是“高效对比新旧虚拟DOM树,找出最小更新集”,React Diff基于三大假设(减少对比复杂度):
同层节点对比:只对比同一层级的虚拟DOM节点,不跨层级对比(若节点跨层级移动,直接删除旧节点,创建新节点)。
类型相同复用节点:若新旧节点type相同、key相同,复用旧节点,仅更新props;类型不同则直接替换。
key唯一标识节点:列表渲染时,key用于标识节点的唯一性,帮助Diff算法快速找到可复用节点,避免错位。
8.3.1 Diff算法流程(基于Fiber)
调和阶段(beginWork)中,针对每个Fiber节点,获取新旧虚拟DOM(oldProps/newProps对应的React Element)。
对比新旧虚拟DOM:
若type不同:标记旧节点为删除,创建新Fiber节点替换。
若type相同、key相同:复用旧Fiber节点,更新props,递归对比子节点。
子节点对比(列表Diff):
无key时:按索引对比,若列表顺序变化,会导致大量节点更新(效率低)。
有key时:通过key建立新旧子节点的映射,快速找到可复用节点,仅移动位置或更新变化节点(效率高)。
8.4 虚拟DOM与Fiber的关系
虚拟DOM是“数据描述”,Fiber是“工作单元”,两者协同工作:
虚拟DOM描述组件的期望状态(what to render)。
Fiber基于虚拟DOM创建/更新工作单元,执行Diff算法,调度渲染任务(how to render)。
调和阶段,Fiber节点的更新依赖虚拟DOM的Diff结果,最终将虚拟DOM的变化转化为真实DOM操作。
8.5 高频面试题
Q:虚拟DOM的核心价值是什么?为什么虚拟DOM能提升性能? A:核心价值:跨平台、减少DOM操作、抽象渲染逻辑。性能提升原因:通过Diff算法找到最小更新集,避免全量DOM更新(DOM操作耗时远高于JS计算)。
Q:React Diff算法的三大假设是什么?key的作用是什么? A:三大假设:同层对比、类型相同复用、key唯一标识。key的作用:帮助Diff算法快速建立新旧节点映射,复用节点,避免列表渲染错位,提升Diff效率。
Q:虚拟DOM和真实DOM的区别? A:① 虚拟DOM是JS对象,存储在内存中,操作成本低;真实DOM是浏览器渲染树节点,操作成本高。② 虚拟DOM与宿主环境无关,可跨平台;真实DOM依赖具体宿主环境(浏览器)。③ 虚拟DOM是中间层,需通过Diff和提交阶段转化为真实DOM。
九、底层数据结构与算法汇总
9.1 核心数据结构
链表:Fiber树(return/child/sibling)、Hooks链表(next),支持中断后恢复、顺序遍历。
队列:Hook更新队列(useState/useReducer的更新任务)、调度任务队列(react-scheduler的优先级任务队列),采用先进先出(FIFO)原则。
哈希表:列表Diff时,通过key建立新旧子节点的哈希映射,快速查找可复用节点。
树:Fiber树(Current/WorkInProgress)、虚拟DOM树,用于描述组件层级结构。
9.2 核心算法
深度优先遍历(DFS):Fiber树调和阶段的遍历方式,通过child→sibling→return指针实现非递归DFS,支持中断。
Diff算法:基于三大假设的同层对比算法,最小化DOM更新。
时间切片算法:react-scheduler基于requestIdleCallback实现,控制任务执行时长,避免阻塞主线程。
优先级调度算法:react-scheduler基于任务优先级和超时时间,调度任务执行顺序,高优先级任务优先执行。
十、四大核心包协同逻辑总结
react:定义核心API(useState、useEffect、React.createElement等),描述组件和状态逻辑。react-reconciler:基于react定义的API,构建Fiber树,执行调和逻辑(Diff、副作用收集),是渲染的核心协调器。react-scheduler:为react-reconciler提供任务调度和时间切片能力,实现优先级驱动的并发渲染。react-dom:适配浏览器环境,在提交阶段执行真实DOM操作,触发生命周期/Hooks回调,将Fiber调和结果转化为可视UI。
四大包协同:react定义“做什么”,react-reconciler+react-scheduler决定“如何高效做”,react-dom负责“最终落地到浏览器”。