You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
if (
!isBatchingUpdates &&
!isRendering &&
lowestPriorityPendingInteractiveExpirationTime !== NoWork
) {
// Synchronously flush pending interactive updates.
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return runWithPriority(UserBlockingPriority, () => {
return fn(a, b);
});
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
React 源码解析:合成事件
React 的事件是通过封装原生事件而得到的合成事件。合成事件无论在性能、兼容上都比原生事件具有优势,而且合成事件在 React 体系中占有非常重要的作用,比如对于
setState
。概念
合成事件机制是:通过拦截封装原生事件,都绑定到 document 元素上,统一管理,绑定,并通过事件冒泡方式传递到绑定的 document 元素上,触发同一类型事件。比如:
虽说我们在 button 元素上书写了
clickButton
函数,但是clickButton
函数实际是绑定到 document 元素上的。当我们点击 button 元素时,通过冒泡到 document 元素,触发clickButton
函数。有一个问题是:如果在原生事件中阻止冒泡,那么 React 合成事件是不会触发的。
因为在 button 元素的原生事件中阻止了冒泡,上面只能执行原生事件而 button 元素的
clickButton
函数不会执行。合成事件的注册
注册流程如下:
createElement
函数得到 DOM 属性 Props。setInitialDOMProperties
函数,判断属性是否为事件类型。ensureListeningTo
函数中,找到 document 对象。listenTo
函数,检查 document 是否绑定了同类事件。如果没有,进入trapCapturedEvent
或者trapBubbledEvent
函数。这里以 click 事件为例,会进入trapBubbledEvent
。trapBubbledEvent
函数中首先提取 dispatch 函数(如果是异步就是dispatchInteractiveEvent
,同步是dispatchEvent
,如果 React 对事件做了支持就会做异步处理,否则采用原生事件也就是同步)。addEventBubbleListener
函数,在 document 中绑定 dispatch 函数。最后在 document 注册的是 dispatch 函数,没有把事件的回调函数保存起来。实际上回调函数是存在 fiber 节点上的,是在事件发生后获取到的,具体在合成事件触发流程中介绍。
合成事件触发流程
大概流程如下:
dispatchInteractiveEvent
、interactiveUpdates
、interactiveUpdates$1
(后缀带 $1 都是打包过后的函数,原函数是其他函数。这里是interactiveUpdates
定义在ReactFiberScheduler.js
文件中) 。dispatchEvent
函数进行事件分发。在dispatchEvent
函数中有一个重要的作用是生成bookKeeping
对象。batchedUpdates
、batchedUpdates$1
(batchedUpdates
定义在ReactFiberScheduler.js
文件中) 两个函数,是关于批量更新的。进入handleTopLevel
函数。runExtractedEventsInBatch
函数,从这里开始进入合成事件对象生成逻辑。然后调用extractEvents
、accumulateInto
。runEventsInBatch
、forEachAccumulated
、executeDispatchesAndReleaseTopLevel
、executeDispatchesAndRelease
函数。executeDispatchesInOrder
函数中,后面会继续调用executeDispatch
、invokeGuardedCallbackAndCatchFirstError
、invokeGuardedCallback
、invokeGuardedCallbackImpl$1
(invokeGuardedCallbackDev
定义在invokeGuardedCallbackImpl.js
文件中)。此时合成事件执行完毕,其中在invokeGuardedCallbackDev
函数中会执行事件的回调函数。合成事件触发的流程已经梳理完成,但是还有几点需要单独拎出来说。
合成事件对象生成
合成事件对象是经过封装的。
clickButton
回调函数的 event 结构如下:在 React 对合成事件做了介绍:
其中提到了事件池的概念:
下面探讨在代码层面合成事件的生成。
在合成事件触发时,会调用
runExtractedEventsInBatch
函数,在runExtractedEventsInBatch
函数中又调用了extractEvents
函数:extractEvents
函数就是生成 event 的地方,在具体介绍前需要搞清楚 数组plugins
的来源。在
extractEvents
函数中,数组plugins
是在初始化时得到的,初始化流程如下:通过调试可以发现数组
plugins
数据都来自于injection.injectEventPluginsByName
函数的参数:以
SimpleEventPlugin
参数为例:SimpleEventPlugin
来自SimpleEventPlugin.js
文件:回到
extractEvents
函数中,其中有下面这段代码:possiblePlugin
就是injection.injectEventPluginsByName
调用时的参数,这里以SimpleEventPlugin
为例。possiblePlugin.extractEvents
四个参数分别是:事件类型、fiber 节点、原生事件对象、DOM 节点。在
SimpleEventPlugin
对象中的extractEvents
函数有下面这段代码:其中
EventConstructor
是根据不同事件类型得到不同结果。以 click 类型为例,EventConstructor
就是SyntheticMouseEvent
。SyntheticMouseEvent
最终会继承基类SyntheticEvent
(其他事件类型一致)。在SyntheticEvent
中会发现封装了preventDefault
、stopPropagation
和浏览器原生事件相同的接口,另外还包括persist
。最后通过
EventConstructor.getPooled
返回值得到 event,然后在经过一些函数处理最终得到合成事件 event。回调函数的查找
在前面大概分析了合成事件对象的生成流程,在这个过程中有一个重要的步骤是查找回调函数。查找回调函数是在合成事件生成的流程中的(这里以
SimpleEventPlugin
为例),具体是从extractEvents
中的accumulateTwoPhaseDispatches
函数开始的:查找回调函数大概流程如上。
因为回调函数存储在 fiber 节点上,所以查找是根据事件类型和 DOM 节点找到相关 fiber 节点进而得到回调函数并赋值到合成事件的
_dispatchListeners
属性上。React 会遍历冒泡经过的所有 DOM 节点,找出存在相同事件类型的回调函数。具体见
traverseTwoPhase
和accumulateDirectionalDispatches
两个函数:在
traverseTwoPhase
函数中,首先找出所有祖先节点,然后 for 循环遍历节点,调用fn
也就是accumulateDirectionalDispatches
函数。如果有回调函数就存储在
event._dispatchListeners
上,如果相同类型事件的回调函数不止一个,那么event._dispatchListeners
就是数组类型。合成事件对
setState
影响合成事件对
setState
的影响主要就是isBatchingUpdates
变量的赋值和 try finally 结构。在合成事件触发流程中,有两个函数对这两点有影响:interactiveUpdates
、batchedUpdates
。以点击为例,在点击事件发生后,执行回调函数之前会先后调用这两个函数:进入
interactiveUpdates
函数时,首先将isBatchingUpdates
状态存起来:const previousIsBatchingUpdates = isBatchingUpdates;
,然后:isBatchingUpdates = true;
。继续进入 try 语句中,会来到batchedUpdates
函数中:此时
isBatchingUpdates
状态为 true,所以previousIsBatchingUpdates
= true。然后又将isBatchingUpdates
的值置为 true。在讲解setState
时,最终都会进入到requestWork
函数中,因为isBatchingUpdates
为 true,requestWork
函数返回,不继续下面的更新。当回调函数执行完成之后,回到
batchedUpdates
函数的 finally 语句中,因为previousIsBatchingUpdates
等于 true,所以不会进入 if 语句,在batchedUpdates
函数中不会执行performSyncWork
。然后会继续返回到
interactiveUpdates
函数的 finally 语句中,因为进入此函数时设置previousIsBatchingUpdates
为 false,而且isRendering
为 false,因此调用performSyncWork
函数更新。会再次进入requestWork
函数中,但是此时isBatchingUpdates
为 false 了,会正常更新。The text was updated successfully, but these errors were encountered: