react事件系统(新版本)
Posted coderlin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react事件系统(新版本)相关的知识,希望对你有一定的参考价值。
上一篇文章讲到,react老版本的事件系统,虽然模拟了事件模拟和冒泡,但是其执行的时机,其实都是在冒泡阶段。
如
react事件处理的俘获阶段实则是在冒泡阶段执行的。而新版本的事件系统则处理了这个问题。如
可以看到react事件处理的俘获事件在俘获的阶段执行了,为什么会在第一个执行呢?因为react17开始,在初始化的时候,就已经向根容器注册了所有的事件了,初始化阶段注册的函数比useEffect注册的时机早。。接下来从18版本的源码开始看看新版本的事件系统跟老版本的区别。
新版本的区别主要体现在事件绑定和事件触发
事件绑定
在新版本的事件系统中,在craeateRoot执行的时候,就会执行listenToAllSupportedEvents一口气向外层容器注册完 全部事件。
先获取了root容器,然后再执行listenToAllSupportedEvents。
看看listenToAllSupportedEvents如何注册全部事件。
listenToAllSupportedEvents
这里需要注意几个点
-
1 allNativeEvents是一个set集合,他保存着81个原生事件。
在上一篇说过,事件插件在初始化的时候就会注册
直接调用registerEvents,他不止会收集react事件跟原生事件的依赖,还会收集所有事件。
用set是因为set不会有重复的值。这样当所有插件注册完毕之后,所有原生事件也就收集完毕了。 -
2 nonDelegatedEvents存放着所有不会冒泡的事件集合。如pause, scroll。
-
3 如果事件不冒泡,即只执行
listenToNativeEvent(domEventName, true, rootContainerElement);
,如果冒泡,那么就会执行
listenToNativeEvent(domEventName, true, rootContainerElement);
和listenToNativeEvent(domEventName, false, rootContainerElement)
其实这里就可以看出区别了,第二个参数就是控制是否注册冒泡的,true表示注册俘获事件,false表示注册冒泡事件。 -
4 listenToNativeEvent函数,他最终调用addTrappedEventListener函数来注册函数。
function addTrappedEventListener()
// 判断事件执行的优先级,返回对应监听器。
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
....
if(isCapturePhaseListener)
// 注册俘获事件
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
else
// 注册冒泡事件。
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
这个函数比较重要的就是两点,一点是createEventListenerWrapperWithPriority
,他会通过优先级获取listener,也就是最终我们注册到容器上要执行的函数。第二点则是通过isCapturePhaseListener
变量(传入给listenToNativeEvent的第二个值),如果是冒泡就注册冒泡事件,否则注册俘获事件。
createEventListenerWrapperWithPriority
根据不同的优先级获取不同的dispatchEvent函数,最后都会通过bind绑定当前事件的名称。也就是说当我们触发事件的时候,最终执行的都是dispatchEvent或者dispatchDiscreteEvent…函数
addEventCaptureListener
& addEventBubbleListener
他们的本质就是调用容器.addEventListener函数,注册事件了。
自此,在createRoot初始化的时候,所有事件注册完毕。此时如果触发一次click事件,那么会执行两次dispatchEvent了,一次是俘获阶段,一次是冒泡阶段,这也是跟16版本不同的地方。
事件触发
一次click事件的发生,会执行dispatchEvent函数。而该函数最终会执行
batchedUpdates是批量更新的逻辑。主要看看dispatchEventsForPlugins
函数。
主要是四个逻辑。
- 通过getEventTarget,传入原生的事件源,然后找到发生点击的dom
- 创建一个待更新队列
- 执行extractEvents收集事件
- 执行processDispatchQueue消费事件。
主要看看extractEvents
函数
他会执行SimpleEventPlugin.extractEvents。
function extractEvents()
// 获取click对应的react事件onClick
const reactName = topLevelEventsToReactNames.get(domEventName);
let SyntheticEventCtor = SyntheticEvent; //默认事件源
// swtich case根据事件获取对应的事件源
// 根据事件类型获取事件源
switch (domEventName)
...
case 'click':
SyntheticEventCtor = SyntheticMouseEvent; //click事件源
break;
...
// 是否是俘获
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// 是否冒泡事件
const accumulateTargetOnly =
!inCapturePhase &&
domEventName === 'scroll';
// 获取真正执行的函数
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
inCapturePhase,
accumulateTargetOnly,
nativeEvent,
);
if (listeners.length > 0)
// 有的话就生成事件源
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
// push进待更新队列
dispatchQueue.push(event, listeners);
这个函数的重点就是
- 根据不同的事件获取事件源。
- 执行accumulateSinglePhaseListeners,获取所有的listener。
- 生成react的事件源,push进dispatchQueue队列。
accumulateSinglePhaseListeners
accumulateSinglePhaseListeners函数会从当前fiber往上遍历直到root,沿途收集所有需要执行的事件。
如图,先判断要收集的名称是俘获还是冒泡,然后通过while循环向上遍历,获取需要执行的函数listener,然后往数组push一个对象,instance, listener, currentTarget
。最后返回数组。
此时的dispatchQueue
大概长这个样
event: ...// react合成的事件源。
listeners: [instance: 当前fiber, listener: f(), currentTaget: dom,...,...]
最后看如何消费dispatchQueue队列。
processDispatchQueue
通常情况下,只有一个事件类型,所有dispatchQueue中只有一个元素,
- 通过for循环消费listeners数组,这里俘获的时候,执行顺序是从后往前的,为的是更好模拟俘获顺序。
- 然后阻止冒泡的逻辑依然是判断event.isPropagationStopped
- 执行executeDispatch函数,他是最终执行listener函数。
自此,事件触发阶段完毕。
一次点击,两次dispatchEvent函数触发。
- 第一次收集俘获事件,然后执行
- 第二次冒泡的时候,收集冒泡事件,然后执行。
总结
(图片来自掘金的《react进阶实践指南》)
-
新版本的在初始化的时候,就已经绑定事件了。而老版本是在遍历fiber节点的props遇到事件注册才会向容器绑定事件。
-
其次就是执行时机的不同,在老版本,所有事件不管是俘获还是冒泡,本质就是在冒泡的时候,遍历fiber树,收集俘获和冒泡的事件,形成事件队列,依次执行,以此模拟事件流。
-
而在新版本,一次事件的触发会执行两次dispatchEvent,第一次收集所有的俘获事件执行。第二次收集所有的冒泡事件执行。执行时机与原生的俘获冒泡时机相同。
-
参考掘金的 《react进阶实践指南》
以上是关于react事件系统(新版本)的主要内容,如果未能解决你的问题,请参考以下文章
Python 3.8 已发布,现在是切换至新版本的好时机吗?
AI大事件 | TensorFlow新版本,Marcus怼深度学习Yann LeCun怼回去