架构
-
接口层
react -
内核层
- 调度器
scheduler,负责执行回调 - 构造器
react-reconciler,调度配合react, react-dom,scheduler - 渲染器
react-dom,生成DOM节点或SSR字符串
- 调度器
工作循环
-
任务调度循环,在
scheduler中采用堆排序算法清空任务队列,执行宏观的任务(task)
-
fiber构造循环,在
react-conciler的ReactFiberWorkLoop中,是任务调度循环的一部分采用深度遍历算法,是任务(task)的一部分,比如task包含:
fiber树的构造,DOM渲染,调度检查
主干逻辑
- 输入:将每次更新视为一次
更新需求 - 注册:
react-reconciler收到更新需求,不会立即更新Fiber树,而是在scheduler注册一个任务task,将更新需求转换成task - 执行调度任务(输出):
scheduler通过任务调度循环执行task,task执行会重新返回到react-reconciler中fiber构造循环:构建出最新的FibercommitRoot:将最新的fiber树渲染到页面上
任务调度循环和fiber构造循环可实现批量更新,可中断渲染
高频对象
- react
ReactElement: React元素,可能是HTML原生,可能是组件ReactComponent:class类型的React元素,会在reconciler阶段生成fiber对象,触发对应的生命周期,调用render方法获取jsx函数式组件:reconciler阶段执行获取子节点
- react-reconciler
- fiber对象:保存了节点类型,副作用,父子兄fiber信息
- UpdateQueue对象:链式队列,有WIP和Current两个队列。更新会按顺序,渲染会跳过低优先级
- Hook对象:hooks背后的对象,是一个链式队列
- Scheduler
- Task对象:小顶堆数组,每个元素是一个Task
启动
-
创建
ReactDomRoot对象暴露
render,unmount方法,引导React启动 -
创建
fiberRoot对象,包含fiber树的全局上下文,fiber构造循环的各种状态,控制执行逻辑 -
创建
HostRootFiber对象,第一个Fiber对象,fiber树的根节点
最终调用updateContainer,进入react-reconciler,调用schedulerUpdateOnFiber

reconciler运作原理
- reconciler的主要作用是:
- 暴露api,如给react-dom暴露
schedulerUpdateOnFiber - 注册调度任务:在
react-scheduler上注册任务 - 执行任务回调:构建
fiber树,与react-dom交互构造与fiber树对应的DOM节点 - 输出:与react-dom交互,渲染DOM节点
- 暴露api,如给react-dom暴露
- 注册调度任务:逻辑在
ReactFiberWorkLoop中-
会根据任务的优先级
performSyncWorkOnRoot直接构造fiber树,还是ensureRootIsScheduled注册调度任务 -
ensureRootIsScheduled首先会判断是否需要注册新的调度然后注册任务,在任务中添加回调,会根据优先级类型使用同步的
performSyncWorkOnRoot还是异步的performConcurrentWorkOnRoot回调构造fiber树
-
- 执行任务回调:
-
同步
performSyncWorkOnRoot构造
fiber树判断异常
提交输出
commitRoot -
异步
performConcuurrentWorkOnRoot检查是否正在
render,是否需要恢复上一次渲染如果渲染被中断,返回新的
performConcuurrentWorkOnRoot等待下一次调用,这就是可中断
-
- 输出:主要执行不同生命周期的副作用队列
commitBeforeMutationEffects:DOM突变前的副作用队列commitMutationEffects:DOM变更的副作用队列commitlayoutEffects:DOM变更后的副作用队列
优先级管理
- fiber树的的
lanePriority,采用位运算,位数越低优先级越高 - scheduler的
schedulerPriority - 用于上面两套系统的转换
ReactPriorityLevel
调度管理
-
核心函数
requestHostCallback:请求及时调度 MessageChannelcancelHostCallback:取消及时调度 scheduledHostCallabck = nulrequestHostTimeout:请求延时调度 setTimeoutcancelHostTimeout:取消延时调度 cancelTimeoutshouldYieldToHost:是否让让出主线程 current ≥ deadline && (needPaint || inputPending)requestPaint:请求请求绘制 needPaint = truegetCurrentTime:获取现在时间 performance.now()forceFrameRate:强制设置让出主线程间隔 yieldInterval = 1000 / fps
-
调度中心
调度中心通过宏任务
MessageChannel异步地请求和消费调度,将任务回调保存在scheduledHostCallabck中,在MessageChannel.port的onmessage中判断callback是否被取消requestHostCallback请求调度performWorkUntilDeadline消费调度hasMoreWork有更多任务,再度请求调度

-
任务
任务创建会根据优先级设置一个过期时间
expirationTime,优先级越高,过期时间越短,任务插入任务队列的sortIndex就是expirationTime -
消费任务
- 调用
requestHostCallback消费任务,flushWork是回调函数,内部调用workLoop循环消费任务队列。 workLoop会不断从taskQueue取出任务,调用currentTask.callback(callback可中断)消费,callback的执行在react-reconciler中构建fiber树,callback可能产生新的回调,重新保存在currentTask.callback。- 每次取出前会判断是否超时
currnetTask.expriationTime > currentTime,应该交还给主线程shouldYieldToHost,还有剩余时间hasTimeRemaining
时间切片:消费每个任务前都会进行超时检查
可中断渲染:每个
task.callback可以自己检测超时,fiber构造过程中,每构造完一个单元performUnitOfWork都会进行超时检查shouldYield,超时就退出fiber树构造循环 - 调用
-
注册任务时的节流防抖
节流:
exsitingCallbackPriority === newCallbackPriority优先级相同,沿用上一个task,无需注册新的task防抖:
existingCallbackNode !== null优先级已经改变,取消当前task,重新注册
整个调度流程:

Fiber树构造的基本概念
-
ReactElement,Fiber,DOM的关系ReactElement是通过书写jsx语法,编译器使用React.createElement转换Fiber是通过ReactElement进行创建的,Fiber树是构造DOM树的数据模型,Fiber树的改动最终体现在DOM树上 -
双缓冲技术
内存中存在两个
Fiber树当前界面的fiber树挂载在
fiberRoot.current正在构造的fiber树在
fiberRoot.alternate正在构造的节点称为
workInProgress -
优先级
有3种优先级,
renderLane,fiberLane和updateLane,只有命位运算&有交集才处理,否则跳过renderLane:当前任务要处理的优先级任务,判断下一次要处理的lane的顺序是pendingLanes,expiredLanes,suspendLanes,pingedLanesrenderLane:当前任务要处理的优先级任务,判断下一次要处理的lane的顺序是pendingLanes,expiredLanes,suspendLanes,pingedLanesfiberLane:包含当前fiber所有update的lane的,fiber复用的条件是fiber.lanes和renderLanes没有交集updateLane:setState可以创建更新,fiber中的updateQueue是当前组件需要更新的更新,updateLane是当前update的优先级
-
栈帧
使用一组全局变量表示当前的活动记录,当
fiber树构造被打断的时候可以依赖他们恢复上一次构造状态。prepareFreshStack会刷新栈帧到初始状态,并创建HostRootFiber.alternate = workInProgress
Fiber树的初次构造
- 启动阶段:采用同步构造fiber树的方法
performSyncWorkOnRoot- 获取当前构造的优先级
getNextLanes,从所有已有的优先级获取最高的lane,初次为最高NoLanes - 清空栈帧
perpareFreshStack,在构造fiber树开始都需要初始化,清空栈帧的时候会创建HostRootFiber.alternate并将workInPregress指向它 - 开始按照深度遍历的顺序构造
- 获取当前构造的优先级
- 构造阶段
- 循环构造,legacy和concurrent的区别是每次调用
performUnitOfWork前都会检查是否需要让渡主线程给浏览器shouldYield - 探寻阶段
beginWork,从HostRootFiber.alternate开始,根据不同的workInProgress.tag类型执行不同的updateXXX(根fiber→updateHostRoot,普通DOM节点→updateHostComponent),最后返回子节点fiber,即为next,next不为空继续循环构造- 根据新的props
fiber.pendingProps和setState得到的updatefiber.updateQueue计算出新的状态fiber.memorizedProps - 执行获取下一级的
ReactElement,Class组件实例挂载到fiber.stateNode调用render;函数组件是直接执行。 - 根据实际情况设置
fiber.flags - 调用
reconcilerChildren生成次级 子节点的fiber
- 根据新的props
- 探寻阶段是如何遍历
updateQueue的:baseQueue保存着尚未处理的更新shared.pending是setState异步添加的更新- 将
shared.pending追加baseQueue末尾,根据renderLanes遍历执行优先级够的update,不够的保存到baseQueue中 - 在
update.callback收集副作用,在commit阶段执行。如果有副作用,标记workInProgress.flag |= Callback
- 回溯阶段:
completeUnitOfWork- 调用
completeWork,为HostComponent,HostText创建DOM实例,设置属性,添加事件,append子节点的DOM;设置fiber.stateNode局部状态(如tag为HostComponent,HostText则是DOM实例) - 将当前
fiber的副作用队列(元素是fiber)添加到父fiber的副作用队列。判断fiber.flags,如果当前节点有副作用,也将当前fiber添加到父fiber的副作用队列 - 如果当前
fiber节点有兄弟节点。将workInProgress设为sibling,继续beginWork。否则向上回溯
- 调用
- 循环构造,legacy和concurrent的区别是每次调用

Fiber树的对比更新
-
更新入口
Class组件的setStateFunction组件的dispatchActionrender重复触发
-
创建更新并入队
创建
update对象,并入队到当前的fiber上,执行scheduleUpdateOnFiber执行输入 -
调度执行
ensureRootIsScheduled,首先markUpdateLaneFromFiberToRoot将当前fiber的更新优先级向当前fiber.lanes,当前fiber.alternate.lanes和childLanes合并,然后向父fiber节点的lanes,alternate.lanes,childLanes合并直到HostRootFiber -
接下来和初次渲染一样,刷新栈帧,从
HostRoot开始深度遍历 -
探寻阶段
beginWork需要判断是不是老节点current ≠= null,- 如果是老节点判断有没有更新(
lanes和当前renderLanes有重合部分),如果不需要更新直接进入completeUnitOfWork阶段 - 如果
includeSomeLanes说明子节点有更新,cloneChildFibers克隆子节点并返回,进入updateXXX更新fiber。复用fiber结构并重置flags,effects属性 - 在
reconcilerChildren阶段会对比新旧fiber决定是否复用- 根据实际情况设置
fiber.flags如新增Placement,删除Delection
- 根据实际情况设置
- 需要删除的
fiber.flags = Delection提前添加到父节点的effects链表中,不会进入completeWork阶段
- 如果是老节点判断有没有更新(
-
回溯阶段
completeUnitOfWork和初次构建一致,遇到current≠=null表明有老节点,标记fiber.flags |= Update,在commit阶段处理

Fiber树的渲染
- 调用
commitRoot渲染 - 构建完成,即将渲染的
fiber树在fiberRoot.finishedWork上,fiber树的特点:- 副作用列队挂在根节点上
finishedWork.firstEffect上 - 最新页面的
DOM对象挂载在fiber树首个HostComponent的stateNode上
- 副作用列队挂在根节点上
- 分为三个阶段:
渲染前,渲染和渲染后 - 渲染前
- 设置全局状态(更新fiber属性)
- 重置全局状态(
workInProgress等) - 处理根节点副作用,如果根节点有副作用,将它添加到副作用链表尾部
- 渲染:分为3个阶段,
DOM突变前,中,后 - DOM突变前,调用
commitBeforeMutationEffects处理副作用,处理带有SnapShot,Passive的fiber节点SnapShot标记可以有2种方式创建- 调用
Class组件的getSnapShotBeforeUpdate生命周期函数 - 调用
clearContainer,清空容器(div#app)
- 调用
Passive标记是使用hook才会创建的,如useEffect。通过scheduleCallback异步执行副作用
- DOM突变,调用
commitMutationEffects处理副作用,这里修改DOM,界面会更新。处理带有ContentReset,Ref,Placement,update,Delection,Hydrating的fiber节点。处理完成后会切换fiber.current = finishedWorkRef类型会清空,在CommitLayoutEffect会重新赋值Placement,update,Delection,Hydrating类型是新增,更新,删除节点,会通过与react-dom包交互完成DOM更新
- DOM突变后会处理带有
Ref,Update,Callback的fiber节点Ref类型会重新进行ref赋值Update,Callabck类型可以在ClassComponent和HostComponet出现- 调用
ClassComponent的生命周期componentDidMount和componetDidUpdate,update的回调update.callback(如this.setState({}, callabck)) HostComponet带有Update标记需要设置原生状态如focus
- 调用
- 渲染后:
- 遍历副作用链表,清空链表让
gc回收 - 调用
ensureRootIsScheduled,检查是否有异步的任务,发起异步调度(如componentDidMount会再次setState) - 检查同步的任务
flushSyncCallabckQueue,如果有再次进入fiber构造循环
- 遍历副作用链表,清空链表让
状态与副作用
fiber对象中的属性有两类属性分别与自身状态和副作用相关
- 状态和副作用影响fiber构建与渲染不同阶段
- 状态相关:在
fiber树构造阶段为节点提供确定的输入,直接影响子节点的生成,是静态的功能 - 副作用相关:在
commitRoot阶段,如果fiber有副作用,会同步/异步执行。是动态的功能
- 状态相关:在
- 影响fiber状态和副作用的属性
- 状态:
pendingProps最新传入的props,和memorizedProps对比可以得出属性是否改变memorizedProps上一次子节点生成时用到的propsupdateQueueupdate更新链表,发起更新创建的update对象会保存在链表中memorizedState上一次子节点生成保存的state
- 副作用
flags标志位,表明副作用类型firstEffect副作用链表,指向第一个fiber对象lastEffect副作用链表,指向最后一个fiber对象nextEffect副作用链表,指向下一个fiber对象
- 状态:
生命周期函数和hook用于直接或间接控制上面两类属性-
Class组件
this.setState发起更新请求,进入reconciler阶段- 状态相关:
constructor可以设置state - 状态相关:
getDerivedStateFromProps让组件在修改props的时候更新state - 状态相关:
shouldComponentUpdate每次state或props修改前调用 - 副作用相关:
getSnapshotBeforeUpdate - 副作用相关:
componentDidMount - 副作用相关:
componentDidUpdate
- 状态相关:
-
函数式组件
useState的setXxx发起更新请求,进入reconciler阶段- 状态相关:
useState可以修改Hook.memorizedState - 副作用相关:
useEffectflags是Passive | Update,在beforeMutation异步执行 - 副作用相关:
useLayoutStateflgas是Update,在afterMutation同步执行
- 状态相关:
-
Hook原理(概览)
- 状态hook与副作用hook区分
- 实现了状态持久化且没有没有副作用的hook:
useState,useReducer,useCallback,useMemo,useContext,useRef等 - 会修改
flber.flags的hook,useEffect,useLayoutEffect - 组合:同时有状态和副作用2种属性:
useDeferredValue,useTransition,useSyncExternalStore等
- 实现了状态持久化且没有没有副作用的hook:
Function类型的fiber节点通过updateFuncitonComponent创建和更新,内部使用renderWithHooks调用函数- 通过Hook API创建
hook对象,Hook对象包含以下属性hook.memorizedState保存在内存中的局部状态hook.baseState由hook.baseQueue合并后的状态hook.baseQueue链表存储高于当前渲染优先级的update对象hook.queue链表存储所有优先级的update对象hook.next指向链表汇中下一个hook对象
- 当前
fiber的hook对象以链表的形式存储在fiber.memorizedState中 fiber首次构造时使用moutWorkInProgressHook构造fiber.memorizedState链表fiber对比更新时使用updateWorkInProgressHook构造fiber.memorizedState链表,维护全局的currentHook和workInProgressHook,将current上的hook对象复制到alternate上
Hook原理(状态)
- 状态初始化:
useState使用mountState,useReducer使用mountReducer创建hook对象,进行- 初始化一个
hook对象,包含上面5个属性 - 将
baseState和memorizedState设置为调用useState的initailState - 初始化
hook.queue对象,pending将来存储发起更新请求的后的update对象 - 创建一个
dispatch方法,调用这个方法发起更新请求dispatchAction - 最后返回
[memorizedState, dispatch]
- 初始化一个
- 状态更新:调用dispatch函数发起一个调度更新请求
- 创建
update对象 - 将
update对象添加到hook.queue.pending链表中 - 发起调度更新
scheduleUpdateOnFiber,多次同步的dispatch会因为调度更新判断渲染优先级相同 不会创建新的task fiber对比更新中调用updateReducer- 将
hook.queue.pending的update链表拼接到hook.baseQueue中 - 遍历
baseQueue,渲染优先级高update的执行,渲染优先级低update的重新添加到baseQueue等待下次执行 - 调用
reducer计算得到memorizedState
- 创建
- 性能优化:
- 在
dispatchAction的时候,如果当前创建的update是queue.penging中第一个update(即本次调度中第一个setXxx) - 直接计算
update后的state,记为eagerState - 对比
eagerState和memorizedState,如果相同直接返回,不会请求调度
- 在

Hook原理(副作用)
-
初始化:
useEffect,useLayoutEffect可以创建hook对象- 创建
hook对象 - 将当前
fiber(workInProgress)的flags标记为fiberFlagsuseEffect是UpdateEffect | PassiveEffectuseLayoutEffect是UpdateEffect
- 创建
effect对象,放在循环链表上,挂载在hook.memorized上;并添加到fiber的链表尾部fiber.lastEffect
- 创建
-
effect对象tag:标记effect类型useEffect是HookHasEffect | HookPassiveuseLayoutEffect是HookHasEffect | HookLayout
create:传入的函数deps:依赖项destory:传入create函数reutrn的回调next:指向下一个effect
-
处理回调,在
commitRoot阶段处理useEffect- 在
commitBeforeMutationEffects阶段执行scheduleCallback调度执行flushPassiveEffects。 flushPassiveEffects会遍历高于当前渲染优先级的effects链表,将effect对象的destory和create分别存储在两个数组中。- 先遍历执行
destory数组,后遍历执行create数组
- 在
useLayoutEffect- 在
commitMutationEffects先遍历effcts链表,同步执行高于当前优先级的destory - 在
commitLayoutEffects遍历effcts链表,同步执行高于当前优先级的create
- 在
-
fiber对比更新阶段 -
desotry函数会直接复用
-
对比deps是否有变化,如果没有,新创建的
effect.tag没有HookHasEffect标志,不会在commitRoot阶段执行 -
组件销毁,会在
commitUnmout阶段遍历执行destory函数

context原理
- 创建context:
- 将
pendingProps.value保存到_currentValue中 - 创建
Provider和Consumer两个ReactElement对象
- 将
- 消费context
prepareToReadContext进行重置操作readContext构造一个contextItem,将它push到当前fiber的workInProgress.dependencies中,最后返回context._currentValue
- 更新context
- 对比
pendingProps和memorizedProps的新旧value, - 如果没有变化进入
bailout阶段对比子节点 - 如果变化,进入
propagateContextChange - 向下查找
fiber,找到所有fiber.dependencies依赖该context的fiber(consumer),创建update入队,并标记节点的tag为forceUpdate。调度更新 - 从
consumer节点,向上查找fiber修改fiber.childLanes表明子节点有改动
- 对比
合成事件
-
不能冒泡的事件
scroll,resize,focue,blur,mouseleave,mouseenter -
采用事件委托在根节点注册事件,不能代理的特殊处理
-
事件绑定:
listenToAllSupportedEvents绑定支持的事件类型,完成原生事件代理addEventBubbleListener监听冒泡事件addEventCaptureListener监听捕获事件
-
调度函数
dispatchEvent,React将不同的事件化为3个等级,采用不同的调度函数disPatchDiscreateEvent:优先级最高,如click,inputdispatchUserBlockingEvent:优先级次之,如scroll,dragdispatchContinuoutEvent:优先级最低,如load,animate
-
事件触发,使用
dispatchEvent触发事件attempToDispatchEvent关联fiber- 定位到原生
DOM,调用getEventTarget - 获取
DOM对应的fiber,getClosestInstanceFromNode - 通过插件系统派发事件
dispatchEventForPluginSystem
- 定位到原生
- 从
targetFiber上遍历,获取所有对应事件(如onClick)的事件回调,回调保存在fiber.memorizedProps中(如fiber.memorizedProps.onClick) - 创建合成事件
SyntheticEvent,添加到派发队列dispatchQueue中。合成事件主要抹平了不同平台的差异 - 执行派发,因为
dispatchQueue是从子节点向父节点收集,capture倒序触发,bubble顺序触发
-
综上:主要步骤是
- 监听原生事件,找到
target元素对应的fiber - 向上遍历
fiber树,收集所有监听本事件的listener - 构建合成事件,遍历
listener数组派发(触发)事件
- 监听原生事件,找到