📢 文章首发博客: 阿宽的博客html
💕 舒适提示: 下边是对React合成事件的源码阅读,全文有点长,可是!若是你真的想知道这鲜为人知的背后内幕,那必定要耐心看下去!前端
最近在作一个功能,而后不当心
踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官网合成事件 的解释,这不看不知道,一看吓一跳...node
SyntheticEvent是个什么鬼?咋冒出来了个事件池?react
我就一个简单的需求功能,为何能扯出这些鬼玩意??git
咱们先简单的来看一看个人需求功能是个啥???github
须要作一个弹窗打开/关闭
的功能,当点击 button
的时候打开,此时打开的状况下,点击弹窗 区域
外,就须要关闭。数组
这简单嘛,直接在 button
上注册一个点击事件,同时在 document.body
注册一个点击事件,而后在 弹窗container
里阻止冒泡,很难嘛?浏览器
class FuckEvent extends React.PureComponent {
state = {
showBox: false
}
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
handleClickBody = () => {
this.setState({
showBox: false
})
}
handleClickButton = () => {
this.setState({
showBox: true
})
}
render() {
return (
<div> <button onClick={this.handleClickButton}>点击我显示弹窗</button> {this.state.showBox && ( <div onClick={e => e.stopPropagation()}>我是弹窗</div> )} </div>
)
}
}
复制代码
很简单嘛,很开心的点击了弹窗区域....缓存
因而...我没了...点击弹窗区域,弹窗也被关闭了。。。what the f**k ?????? 难道冒泡没有用 ?bash
带着这个问题,我走上了不归之路
...
咱们都知道,什么是事件委托,(不知道的出门左拐 👈) 在前端刀耕火种时期,事件委托但是爸爸
事件委托解决了庞大的数据列表时,无需为每一个列表项绑定事件监听。同时能够动态挂载元素无需做额外的事件监听处理。
你看,事件委托那么牛 13,你以为 React 会不用?呵,React 不只用了,还用的很是溜 ~
怎么说呢,react 它接管了浏览器事件的优化策略,而后自身实现了一套本身的事件机制,并且特别贴心,就跟你男友同样,它把浏览器的不一样差别,都帮你消除了 ~
React 实现了一个合成事件层,就是这个事件层,把 IE 和 W3C 标准之间的兼容问题给消除了。
📌 那么问题来了,什么是合成事件与原生事件????
原生事件: 在 componentDidMount生命周期
里边进行addEventListener
绑定的事件
合成事件: 经过 JSX 方式绑定的事件,好比 onClick={() => this.handle()}
还记得上边的那个例子吗?咱们在弹窗的 DOM 元素上绑定了一个事件,进行阻止冒泡
{
this.state.showBox && <div onClick={e => e.stopPropagation()}>我是弹窗</div>
}
复制代码
而后在componentDidMount生命周期
里边对 body 进行了 click 的绑定
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
复制代码
咱们去分析一下,由于合成事件的触发是基于浏览器的事件机制来实现的,经过冒泡机制冒泡到最顶层元素,而后再由 dispatchEvent 统一去处理
回顾一下浏览器事件机制
Document 上边是 Window,这里截的是《JavaScript 高级程序设计》书籍里的图片
浏览器事件的执行须要通过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。
🙋 Question: 此时对于合成事件进行阻止,原生事件会执行吗?答案是: 会!
📢 Answer: 由于原生事件先于合成事件执行 (我的理解: 注册的原生事件已经执行,而合成事件处于目标阶段,它阻止的冒泡只是阻止合成的事件冒泡,可是原生事件在捕获阶段就已经执行了)
React 本身实现了这么一套事件机制,它在 DOM 事件体系基础上作了改进,减小了内存的消耗,而且最大程度上解决了 IE 等浏览器的不兼容问题
那它有什么特色?
React 上注册的事件最终会绑定在document
这个 DOM 上,而不是 React 组件对应的 DOM(减小内存开销就是由于全部的事件都绑定在 document 上,其余节点没有绑定事件)
React 自身实现了一套事件冒泡机制,因此这也就是为何咱们 event.stopPropagation()
无效的缘由。
React 经过队列的形式,从触发的组件向父组件回溯,而后调用他们 JSX 中定义的 callback
React 有一套本身的合成事件 SyntheticEvent
,不是原生的,这个能够本身去看官网
React 经过对象池的形式管理合成事件对象的建立和销毁,减小了垃圾的生成和新对象内存的分配,提升了性能
看到这里,应该对 React 合成事件有一个简单的了解了吧,咱们接着去看一看源码 ~
咱们在 ReactBrowserEventEmitter.js
文件中能够看到,React 合成系统框架图
/**
* React和事件系统概述:
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | application | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
*/
复制代码
源码里边的一大串英文解释,我帮大家 google 翻译了,简单来说就是:
Top-level delegation 用于捕获最原始的浏览器事件,它主要由 ReactEventListener 负责,ReactEventListener 被注入后能够支持插件化的事件源,这一过程发生在主线程。
React 对事件进行规范化和重复数据删除,以解决浏览器的怪癖。这能够在工做线程中完成。
将这些本地事件(具备关联的顶级类型用来捕获它)转发到EventPluginHub
,后者将询问插件是否要提取任何合成事件。
而后,EventPluginHub 将经过为每一个事件添加“dispatches”(关心该事件的侦听器和 ID 的序列)来对其进行注释来进行处理。
再接着,EventPluginHub 会调度分派事件.
❗ 建议直接去看英文注释,翻译可能不是很标准。
看会上边的框架图,咱们得先知道一下这些都是个啥玩意,直接看名称,也可以知道 :
👇 下面咱们来一步一步的看它是怎么工做的
React 中注册一个事件贼简单,就好比这样:
class TaskEvent extends Reac.PureComponent {
render() {
return (
<div onClick={() => { console.log('我是注册事件') }} > 呵呵呵 </div>
)
}
}
复制代码
ok,洋洋洒洒的写下这段代码,它是如何被注册到 React 事件系统中的?
组件在建立 mountComponent 和更新 updateComponent 的时候,都会调用 _updateDOMProperties()
方法
📢 舒适提示,这快的源码是 react 15.6.1 的源码,可是我在 github 上找对应的版本进去,竟然是 Pages Not Found ... 这里就用我翻阅资料的文章中对这个注册事件的源码解释了
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
// ...
var props = this._currentElement.props;
// ...
this._updateDOMProperties(null, props, transaction);
// ...
}
复制代码
_updateDOMProperties: function (lastProps, nextProps, transaction) {
// ...
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
continue;
}
if (propKey === STYLE) {
// ...
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// 若是是props这个对象直接声明的属性,而不是从原型链中继承而来的,则处理它
// 对于mountComponent,lastProp为null。updateComponent两者都不为null。unmountComponent则nextProp为null
if (nextProp) {
// mountComponent和updateComponent中,enqueuePutListener注册事件
enqueuePutListener(this, propKey, nextProp, transaction);
} else if (lastProp) {
// unmountComponent中,删除注册的listener,防止内存泄漏
deleteListener(this, propKey);
}
}
}
}
复制代码
上边的代码很清楚告诉你,经过 enqueuePutListener()
方法进行注册事件,咱们接着去看看这是个啥玩意
function enqueuePutListener(inst, registrationName, listener, transaction) {
if (transaction instanceof ReactServerRenderingTransaction) {
return
}
var containerInfo = inst._hostContainerInfo
var isDocumentFragment =
containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE
// 找到document
var doc = isDocumentFragment
? containerInfo._node
: containerInfo._ownerDocument
// 注册事件,将事件注册到document上
listenTo(registrationName, doc)
// 存储事件,放入事务队列中
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
})
}
复制代码
💢 看到没,这个 enqueuePutListener()
就只干了两个事情 :
经过调用 listenTo
把事件注册到 document 上 (这就是前边说的 React 上注册的事件最终会绑定在document
这个 DOM 上)
事务方式调用 putListener
存储事件 (就是把 React 组件内的全部事件统一的存放到一个对象里,缓存起来,为了在触发事件的时候能够查找到对应的方法去执行)
虽说不要贴代码,可是!直接看源码真的是简单明了啊,👉 listenTo 源码
📢 注意,react 版本是目前 github master 分支代码
咱们来看一下代码
export function listenTo( registrationName: string, mountAt: Document | Element | Node ): void {
const listeningSet = getListeningSetForElement(mountAt)
const dependencies = registrationNameDependencies[registrationName]
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i]
// 调用该方法进行注册
listenToTopLevel(dependency, mountAt, listeningSet)
}
}
复制代码
registrationName 就是传过来的 onClick,而变量 registrationNameDependencies 是一个存储了 React 事件名与浏览器原生事件名对应的一个 Map,能够经过这个 map 拿到相应的浏览器原生事件名
export function listenToTopLevel( topLevelType: DOMTopLevelEventType, mountAt: Document | Element | Node, listeningSet: Set<DOMTopLevelEventType | string> ): void {
if (!listeningSet.has(topLevelType)) {
switch (topLevelType) {
//...
case TOP_CANCEL:
case TOP_CLOSE:
if (isEventSupported(getRawEventName(topLevelType))) {
trapCapturedEvent(topLevelType, mountAt) // 捕获阶段
}
break
default:
const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1
if (!isMediaEvent) {
trapBubbledEvent(topLevelType, mountAt) // 冒泡阶段
}
break
}
listeningSet.add(topLevelType)
}
}
复制代码
上边忽略部分源码,咱们看到,注册事件的入口是 listenTo 方法, 经过对dependencies
循环调用listenToTopLevel()
方法,在该方法中调用 trapCapturedEvent 和 trapBubbledEvent 来注册捕获和冒泡事件。
下边仅对 trapCapturedEvent
进行分析,👉 trapCapturedEvent 源码地址,trapBubbledEvent 源码地址
// 捕获阶段
export function trapCapturedEvent( topLevelType: DOMTopLevelEventType, element: Document | Element | Node ): void {
trapEventForPluginEventSystem(element, topLevelType, true)
}
// 冒泡阶段
export function trapBubbledEvent( topLevelType: DOMTopLevelEventType, element: Document | Element | Node ): void {
trapEventForPluginEventSystem(element, topLevelType, false)
}
复制代码
function trapEventForPluginEventSystem( element: Document | Element | Node, topLevelType: DOMTopLevelEventType, capture: boolean // 决定捕获仍是冒泡阶段 ): void {
let listener
switch (getEventPriority(topLevelType)) {
}
const rawEventName = getRawEventName(topLevelType)
if (capture) {
addEventCaptureListener(element, rawEventName, listener)
} else {
addEventBubbleListener(element, rawEventName, listener)
}
}
复制代码
😝 这里咱们就能知道,捕获事件经过addEventCaptureListener()
,而冒泡事件经过addEventBubbleListener()
// 捕获
export function addEventCaptureListener( element: Document | Element | Node, eventType: string, listener: Function ): void {
element.addEventListener(eventType, listener, true)
}
// 冒泡
export function addEventBubbleListener( element: Document | Element | Node, eventType: string, listener: Function ): void {
element.addEventListener(eventType, listener, false)
}
复制代码
还记得上边的 enqueuePutListener()
中,咱们将事件放入到事务队列嘛?
function enqueuePutListener(inst, registrationName, listener, transaction) {
//...
// 注册事件,将事件注册到document上
listenTo(registrationName, doc)
// 存储事件,放入事务队列中
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
})
}
复制代码
没错,就是 putListener
这个玩意,咱们能够看一下代码
putListener: function (inst, registrationName, listener) {
// 用来标识注册了事件,好比onClick的React对象。key的格式为'.nodeId', 只用知道它能够标示哪一个React对象就能够了
// step1: 获得组件惟一标识
var key = getDictionaryKey(inst);
// step2: 获得listenerBank对象中指定事件类型的对象
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
// step3: 将listener事件回调方法存入listenerBank[registrationName][key]中,好比listenerBank['onclick'][nodeId]
// 全部React组件对象定义的全部React事件都会存储在listenerBank中
bankForRegistrationName[key] = listener;
// ...
}
// 拿到组件惟一标识
var getDictionaryKey = function (inst) {
return '.' + inst._rootNodeID;
};
复制代码
既然事件已经委托注册到 document
上了,那么事件触发的时候,确定须要一个事件分发的过程,流程也很简单,既然事件存储在 listenrBank
中,那么我只须要找到对应的事件类型,而后执行事件回调就 ok 了
📢 注意: 因为元素自己并无注册任何事件,而是委托到了 document 上,因此这个将被触发的事件是 React 自带的合成事件,而非浏览器原生事件
首先找到事件触发的DOM
和React Component
,找真实的 DOM 仍是很好找的,在getEventTarget 源码中能够看到:
// 源码看这里: https://github.com/facebook/react/blob/master/packages/react-dom/src/events/ReactDOMEventListener.js#L419
const nativeEventTarget = getEventTarget(nativeEvent)
let targetInst = getClosestInstanceFromNode(nativeEventTarget)
复制代码
function getEventTarget(nativeEvent) {
let target = nativeEvent.target || nativeEvent.srcElement || window
if (target.correspondingUseElement) {
target = target.correspondingUseElement
}
return target.nodeType === TEXT_NODE ? target.parentNode : target
}
复制代码
这个 nativeEventTarget
对象上挂在了一个以 __reactInternalInstance
开头的属性,这个属性就是 internalInstanceKey
,其值就是当前 React 实例对应的 React Component
继续看源码: dispatchEventForPluginEventSystem()
function dispatchEventForPluginEventSystem( topLevelType: DOMTopLevelEventType, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber ): void {
const bookKeeping = getTopLevelCallbackBookKeeping(
topLevelType,
nativeEvent,
targetInst,
eventSystemFlags
)
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedEventUpdates(handleTopLevel, bookKeeping)
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping)
}
}
复制代码
看到了嘛,batchedEventUpdates()
批量更新,它的工做是把当前触发的事件放到了批处理队列中。handleTopLevel 是事件分发的核心所在
👉 源码在这里: handleTopLevel
function handleTopLevel(bookKeeping: BookKeepingInstance) {
let targetInst = bookKeeping.targetInst
// Loop through the hierarchy, in case there's any nested components.
// It's important that we build the array of ancestors before calling any
// event handlers, because event handlers can modify the DOM, leading to
// inconsistencies with ReactMount's node cache. See #1105.
let ancestor = targetInst
do {
if (!ancestor) {
const ancestors = bookKeeping.ancestors
;((ancestors: any): Array<Fiber | null>).push(ancestor)
break
}
const root = findRootContainerNode(ancestor)
if (!root) {
break
}
const tag = ancestor.tag
if (tag === HostComponent || tag === HostText) {
bookKeeping.ancestors.push(ancestor)
}
ancestor = getClosestInstanceFromNode(root)
} while (ancestor)
}
复制代码
这里直接看上边的英文注释,讲的很清楚,主要就是事件回调可能会改变 DOM 结构,因此要先遍历层次结构,以防存在任何嵌套的组件,而后缓存起来。
而后继续这个方法
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i]
// getEventTarget上边有讲到
const eventTarget = getEventTarget(bookKeeping.nativeEvent)
const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType)
const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent)
runExtractedPluginEventsInBatch(
topLevelType,
targetInst,
nativeEvent,
eventTarget,
bookKeeping.eventSystemFlags
)
}
复制代码
这里就是一个 for 循环来遍历这个 React Component 及其全部的父组件,而后执行runExtractedPluginEventsInBatch()
方法
从上面的事件分发中可见,React 自身实现了一套冒泡机制。从触发事件的对象开始,向父元素回溯,依次调用它们注册的事件 callback。
上边讲到的 runExtractedPluginEventsInBatch()
方法就是事件执行的入口了,经过源码,咱们能够知道,它干了两件事
👉 runExtractedPluginEventsInBatch 源码
export function runExtractedPluginEventsInBatch( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, eventSystemFlags: EventSystemFlags ) {
// step1 : 构造合成事件
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
)
// step2 : 批处理
runEventsInBatch(events)
}
复制代码
咱们来看看相关的代码 extractPluginEvents()
和 runEventsInBatch()
function extractPluginEvents( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, eventSystemFlags: EventSystemFlags ): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
let events = null
for (let i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i]
if (possiblePlugin) {
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
)
if (extractedEvents) {
events = accumulateInto(events, extractedEvents)
}
}
}
return events
}
复制代码
首先会去遍历 plugins
,相关代码在: plugins 源码,这个 plugins 就是全部事件合成 plugins 的集合数组,这些 plugins 是在 EventPluginHub
初始化时候注入的
// 📢 源码地址 : https://github.com/facebook/react/blob/master/packages/legacy-events/EventPluginHub.js#L80
export const injection = {
injectEventPluginOrder,
injectEventPluginsByName
}
复制代码
// 📢 源码地址 : https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOMClientInjection.js#L26
EventPluginHubInjection.injectEventPluginOrder(DOMEventPluginOrder)
EventPluginHubInjection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin
})
复制代码
打住,这里不展开分析,咱们继续看extractEvents
的逻辑代码
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
)
if (extractedEvents) {
events = accumulateInto(events, extractedEvents)
}
复制代码
由于 const possiblePlugin: PluginModule = plugins[i], 类型是 PluginModule,咱们能够去 👉SimpleEventPlugin 源码去看一下 extractEvents
到底干了啥
extractEvents: function() {
const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]
if (!dispatchConfig) {
return null
}
//...
}
复制代码
首先,看下 topLevelEventsToDispatchConfig
这个对象中有没有 topLevelType 这个属性,只要有,那么说明当前事件可使用 SimpleEventPlugin
构造合成事件
函数里边定义了 EventConstructor
,而后经过 switch...case
语句进行赋值
extractEvents: function() {
//...
let EventConstructor
switch (topLevelType) {
// ...
case DOMTopLevelEventTypes.TOP_POINTER_UP:
EventConstructor = SyntheticPointerEvent
break
default:
EventConstructor = SyntheticEvent
break
}
}
复制代码
总之就是赋值给 EventConstructor
,若是你想更加了解SyntheticEvent
,请点击这里
设置好了EventConstructor
以后,这个方法继续执行
extractEvents: function() {
//...
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
)
accumulateTwoPhaseDispatches(event)
return event
}
复制代码
这一段代码的意思就是,从 event 对象池中取出合成事件,这里的 getPooled()
方法其实在在 SyntheticEvent
初始化的时候就被设置好了,咱们来看一下代码
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = []
// 就是这里设置了getPooled
EventConstructor.getPooled = getPooledEvent
EventConstructor.release = releasePooledEvent
}
SyntheticEvent.extend = function(Interface) {
//...
addEventPoolingTo(Class)
return Class
}
addEventPoolingTo(SyntheticEvent)
复制代码
看到这里,咱们知道,getPooled
就是 getPooledEvent
,那咱们去看看getPooledEvent
作了啥玩意
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop()
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeInst
)
return instance
}
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeInst
)
}
复制代码
首先呢,会先去对象池中,看一下 length 是否为 0,若是是第一次事件触发,那很差意思,你须要 new EventConstructor
了,若是后续再次触发事件的时候,直接从对象池中取,也就是直接 instance = EventConstructor.eventPool.pop()
出来的完事了
ok,咱们暂时就讲到这,咱们继续说一说事件执行的另外一个重要操做: 批处理 runEventsInBatch(events)
批处理主要是经过 runEventQueueInBatch(events)
进行操做,咱们来看看源码: 👉 runEventQueueInBatch 源码
export function runEventsInBatch( events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null ) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events)
}
// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
const processingEventQueue = eventQueue
eventQueue = null
if (!processingEventQueue) {
return
}
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel)
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.'
)
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError()
}
复制代码
这个方法首先会将当前须要处理的 events 事件,与以前没有处理完毕的队列调用 accumulateInto
方法按照顺序进行合并,组合成一个新的队列
若是processingEventQueue
这个为空,gg,没有处理的事件,退出,不然调用 forEachAccumulated()
,源码看这里: forEachAccumulated 源码
function forEachAccumulated<T>( arr: ?(Array<T> | T), cb: (elem: T) => void, scope: ?any ) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope)
} else if (arr) {
cb.call(scope, arr)
}
}
复制代码
这个方法就是先看下事件队列 processingEventQueue
是否是个数组,若是是数组,说明队列中不止一个事件,则遍历队列,调用 executeDispatchesAndReleaseTopLevel
,不然说明队列中只有一个事件,则无需遍历直接调用便可
📢 executeDispatchesAndReleaseTopLevel 源码
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event)
if (!event.isPersistent()) {
event.constructor.release(event)
}
}
}
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e)
}
复制代码
export function executeDispatchesInOrder(event) {
const dispatchListeners = event._dispatchListeners
const dispatchInstances = event._dispatchInstances
if (__DEV__) {
validateEventDispatches(event)
}
if (Array.isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(event, dispatchListeners[i], dispatchInstances[i])
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances)
}
event._dispatchListeners = null
event._dispatchInstances = null
}
复制代码
首先对拿到的事件上挂载的 dispatchListeners
,就是全部注册事件回调函数的集合,遍历这个集合,若是event.isPropagationStopped() = ture
,ok,break 就行了,由于说明在此以前触发的事件已经调用 event.stopPropagation()
,isPropagationStopped 的值被置为 true,当前事件以及后面的事件做为父级事件就不该该再被执行了
这里当 event.isPropagationStopped()为 true 时,中断合成事件的向上遍历执行,也就起到了和原生事件调用 stopPropagation 相同的效果 若是循环没有被中断,则继续执行 executeDispatch
方法,至于这个方法,源码地址献上: executeDispatch 源码地址
还有...
没有后续了,写不动了,接下来你们自行去看源码吧,从中午看踩坑,而后经过 event.nativeEvent.stopImmediatePropagation
解决问题以后,就开始翻阅相关博客文章,去看源码,我炸了,中午 2 点到晚上 10 点,都在看这玩意,我已经吐了,OMG