React中的合成事件

React自己实现了一套高效的事件注册、存储、分发和重用逻辑,在DOM事件体系基础上做了很大改进,减少了内存消耗,简化了事件逻辑,并最大程度地解决了IE等浏览器的不兼容问题。

描述

React的合成事件SyntheticEvent实际上就是React自己在内部实现的一套事件处理机制,它是浏览器的原生事件的跨浏览器包装器,除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括stopPropagation()preventDefault(),合成事件与浏览器的原生事件不同,也不会直接映射到原生事件,也就是说通常不要使用addEventListener为已创建的DOM元素添加监听器,而应该直接使用React中定义的事件机制,而且在混用的情况下原生事件如果定义了阻止冒泡可能会阻止合成事件的执行,当然如果确实需要使用原生事件去处理需求,可以通过事件触发传递的SyntheticEvent对象的nativeEvent属性获得原生Event对象的引用,React中的事件有以下几个特点:

  • React上注册的事件最终会绑定在document这个DOM上,而不是React组件对应的DOM,通过这种方式减少内存开销,所有的事件都绑定在document上,其他节点没有绑定事件,实际上就是事件委托的。
  • React自身实现了一套事件冒泡机制,使用React实现的Event对象与原生Event对象不同,不能相互混用。
  • React通过队列的形式,从触发的组件向父组件回溯,然后调用他们JSX中定义的callback
  • React的合成事件SyntheticEvent与浏览器的原生事件不同,也不会直接映射到原生事件。
  • React通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能。

对于每个SyntheticEvent对象都包含以下属性:

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type

支持的合成事件一览,注意以下的事件处理函数在冒泡阶段被触发,如需注册捕获阶段的事件处理函数,则应为事件名添加Capture,例如处理捕获阶段的点击事件请使用onClickCapture,而不是onClick

<!-- 剪贴板事件 -->
onCopy onCut onPaste<!-- 复合事件 -->
onCompositionEnd onCompositionStart onCompositionUpdate<!-- 键盘事件 -->
onKeyDown onKeyPress onKeyUp<!-- 焦点事件 -->
onFocus onBlur<!-- 表单事件 -->
onChange onInput onInvalid onReset onSubmit <!-- 通用事件 -->
onError onLoad<!-- 鼠标事件 -->
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp<!-- 指针事件 -->
onPointerDown onPointerMove onPointerUp onPointerCancel onGotPointerCapture
onLostPointerCapture onPointerEnter onPointerLeave onPointerOver onPointerOut<!-- 选择事件 -->
onSelect<!-- 触摸事件 -->
onTouchCancel onTouchEnd onTouchMove onTouchStart<!-- UI 事件 -->
onScroll<!-- 滚轮事件 -->
onWheel<!-- 媒体事件 -->
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted
onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay
onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend
onTimeUpdate onVolumeChange onWaiting<!-- 图像事件 -->
onLoad onError<!-- 动画事件 -->
onAnimationStart onAnimationEnd onAnimationIteration<!-- 过渡事件 -->
onTransitionEnd<!-- 其他事件 -->
onToggle<!-- https://zh-hans.reactjs.org/docs/events.html -->

示例

一个简单的示例,同时绑定在一个DOM上的原生事件与React事件,因为原生事件阻止冒泡而导致React事件无法执行,同时我们也可以看到React传递的event并不是原生Event对象的实例,而是React自行实现维护的一个event对象。

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>React</title>
</head><body><div id="root"></div>
</body>
<script src="https://unpkg.zhimg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.zhimg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.zhimg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">class ReactEvent extends React.PureComponent {componentDidMount(){ document.getElementById("btn-reactandnative").addEventListener("click", (e) => {console.log("原生事件执行", "handleNativeAndReact");console.log("event instanceof Event:", e instanceof Event);e.stopPropagation(); // 阻止冒泡即会影响了React的事件执行});}handleNativeAndReact = (e) => {console.log("React事件执行", "handleNativeAndReact");console.log("event instanceof Event:", e instanceof Event);}handleClick = (e) => {console.log("React事件执行", "handleClick");console.log("event instanceof Event:", e instanceof Event);}render() {return (<div className="pageIndex"><button id="btn-confirm" onClick={this.handleClick}>React 事件</button><button id="btn-reactandnative" onClick={this.handleNativeAndReact}>原生 + React 事件</button></div>)}}var vm = ReactDOM.render(<><ReactEvent /></>,document.getElementById("root"));
</script></html>

React事件系统

简单来说,在挂载的时候,通过listenerBank把事件存起来了,触发的时候document进行dispatchEvent,找到触发事件的最深的一个节点,向上遍历拿到所有的callback放在eventQueue,根据事件类型构建event对象,遍历执行eventQueue,不简单点说,我们可以查看一下React对于事件处理的源码实现,commit id4ab6305TAGReact16.10.2,在React17不再往document上挂事件委托,而是挂到DOM容器上,目录结构都有了很大更改,我们还是依照React16,首先来看一下事件的处理流程。

/*** Summary of `ReactBrowserEventEmitter` event handling:**  - Top-level delegation is used to trap most native browser events. This*    may only occur in the main thread and is the responsibility of*    ReactDOMEventListener, which is injected and can therefore support*    pluggable event sources. This is the only work that occurs in the main*    thread.**  - We normalize and de-duplicate events to account for browser quirks. This*    may be done in the worker thread.**  - Forward these native events (with the associated top-level type used to*    trap it) to `EventPluginHub`, which in turn will ask plugins if they want*    to extract any synthetic events.**  - The `EventPluginHub` will then process each event by annotating them with*    "dispatches", a sequence of listeners and IDs that care about that event.**  - The `EventPluginHub` then dispatches the events.*//*** React和事件系统概述:** +------------+    .* |    DOM     |    .* +------------+    .*       |           .*       v           .* +------------+    .* | ReactEvent |    .* |  Listener  |    .* +------------+    .                         +-----------+*       |           .               +--------+|SimpleEvent|*       |           .               |         |Plugin     |* +-----|------+    .               v         +-----------+* |     |      |    .    +--------------+                    +------------+* |     +-----------.--->|EventPluginHub|                    |    Event   |* |            |    .    |              |     +-----------+  | Propagators|* | ReactEvent |    .    |              |     |TapEvent   |  |------------|* |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|* |            |    .    |              |     +-----------+  |  utilities |* |     +-----------.--->|              |                    +------------+* |     |      |    .    +--------------+* +-----|------+    .                ^        +-----------+*       |           .                |        |Enter/Leave|*       +           .                +-------+|Plugin     |* +-------------+   .                         +-----------+* | application |   .* |-------------|   .* |             |   .* |             |   .* +-------------+   .*                   .*/

packages\react-dom\src\events\ReactBrowserEventEmitter.js中就描述了上边的流程,并且还有相应的英文注释,使用google翻译一下,这个太概述了,所以还是需要详细描述一下,在事件处理之前,我们编写的JSX需要经过babel的编译,创建虚拟DOM,并处理组件props,拿到事件类型和回调fn等,之后便是事件注册、存储、合成、分发、执行阶段。

  • Top-level delegation用于捕获最原始的浏览器事件,它主要由ReactEventListener负责,ReactEventListener被注入后可以支持插件化的事件源,这一过程发生在主线程。
  • React对事件进行规范化和重复数据删除,以解决浏览器的问题,这可以在工作线程中完成。
  • 将这些本地事件(具有关联的顶级类型用来捕获它)转发到EventPluginHub,后者将询问插件是否要提取任何合成事件。
  • 然后EventPluginHub将通过为每个事件添加dispatches(引用该事件的侦听器和ID的序列)来对其进行注释来进行处理。
  • 再接着,EventPluginHub会调度分派事件。

事件注册

首先会调用setInitialDOMProperties()判断是否在registrationNameModules列表中,在的话便注册事件,列表包含了可以注册的事件。

// packages\react-dom\src\client\ReactDOMComponent.js line 308
function setInitialDOMProperties(tag: string,domElement: Element,rootContainerElement: Element | Document,nextProps: Object,isCustomComponentTag: boolean,
): void {for (const propKey in nextProps) {if (!nextProps.hasOwnProperty(propKey)) {continue;}const nextProp = nextProps[propKey];if (propKey === STYLE) {if (__DEV__) {if (nextProp) {// Freeze the next style object so that we can assume it won't be// mutated. We have already warned for this in the past.Object.freeze(nextProp);}}// Relies on `updateStylesByID` not mutating `styleUpdates`.setValueForStyles(domElement, nextProp);}else if(/* ... */){// ...} else if (registrationNameModules.hasOwnProperty(propKey)) { // 对事件名进行合法性检验,只有合法的事件名才会被识别并进行事件绑定if (nextProp != null) {if (__DEV__ && typeof nextProp !== 'function') {warnForInvalidEventListener(propKey, nextProp);}ensureListeningTo(rootContainerElement, propKey); // 开始注册事件}} else if (nextProp != null) {setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);}}
}

如果事件名合法而且是一个函数的时候,就会调用ensureListeningTo()方法注册事件。ensureListeningTo会判断rootContainerElement是否为document或是Fragment,如果是则直接传递给listenTo,如果不是则通过ownerDocument来获取其根节点,对于ownerDocument属性,定义是这样的,ownerDocument可返回某元素的根元素,在HTMLHTML文档本身是元素的根元素,所以可以说明其实大部分的事件都是注册在document上面的,之后便是调用listenTo方法实际注册。

// packages\react-dom\src\client\ReactDOMComponent.js line 272
function ensureListeningTo(rootContainerElement: Element | Node,registrationName: string,
): void {const isDocumentOrFragment =rootContainerElement.nodeType === DOCUMENT_NODE ||rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;const doc = isDocumentOrFragment? rootContainerElement: rootContainerElement.ownerDocument;listenTo(registrationName, doc);
}

listenTo()方法中比较重要的就是registrationNameDependencies的概念,对于不同的事件,React会同时绑定多个事件来达到统一的效果。此外listenTo()方法还默认将事件通过trapBubbledEvent绑定,将onBluronFocusonScroll等事件通过trapCapturedEvent绑定,因为这些事件没有冒泡行为,invalidsubmitreset事件以及媒体等事件绑定到当前DOM上。

// packages\react-dom\src\events\ReactBrowserEventEmitter.js line 128
export function listenTo(registrationName: string, // 事件的名称,即为上面的propKey(如onClick)mountAt: Document | Element | Node, // 事件注册的目标容器
): void {// 获取目标容器已经挂载的事件列表对象,如果没有则初始化为空对象const listeningSet = getListeningSetForElement(mountAt);// 获取对应事件的依赖事件,比如onChange会依赖TOP_INPUT、TOP_FOCUS等一系列事件const dependencies = registrationNameDependencies[registrationName];// 遍历所有的依赖,并挨个进行绑定for (let i = 0; i < dependencies.length; i++) {const dependency = dependencies[i];listenToTopLevel(dependency, mountAt, listeningSet);}
}export function listenToTopLevel(topLevelType: DOMTopLevelEventType,mountAt: Document | Element | Node,listeningSet: Set<DOMTopLevelEventType | string>,
): void {if (!listeningSet.has(topLevelType)) {// 针对不同的事件来判断使用事件捕获还是事件冒泡switch (topLevelType) {case TOP_SCROLL:trapCapturedEvent(TOP_SCROLL, mountAt);break;case TOP_FOCUS:case TOP_BLUR:trapCapturedEvent(TOP_FOCUS, mountAt);trapCapturedEvent(TOP_BLUR, mountAt);// We set the flag for a single dependency later in this function,// but this ensures we mark both as attached rather than just one.listeningSet.add(TOP_BLUR);listeningSet.add(TOP_FOCUS);break;case TOP_CANCEL:case TOP_CLOSE:// getRawEventName会返回真实的事件名称,比如onChange => onchangeif (isEventSupported(getRawEventName(topLevelType))) {trapCapturedEvent(topLevelType, mountAt);}break;case TOP_INVALID:case TOP_SUBMIT:case TOP_RESET:// We listen to them on the target DOM elements.// Some of them bubble so we don't want them to fire twice.break;default:// 默认将除了媒体事件之外的所有事件都注册冒泡事件// 因为媒体事件不会冒泡,所以注册冒泡事件毫无意义const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1;if (!isMediaEvent) {trapBubbledEvent(topLevelType, mountAt);}break;}// 表示目标容器已经注册了该事件listeningSet.add(topLevelType);}
}

之后就是熟知的对事件的绑定,以事件冒泡trapBubbledEvent()为例来描述处理流程,可以看到其调用了trapEventForPluginEventSystem方法。

// packages\react-dom\src\events\ReactDOMEventListener.js line 203
export function trapBubbledEvent(topLevelType: DOMTopLevelEventType,element: Document | Element | Node,
): void {trapEventForPluginEventSystem(element, topLevelType, false);
}

可以看到React将事件分成了三类,优先级由低到高:

  • DiscreteEvent离散事件,例如blurfocusclicksubmittouchStart,这些事件都是离散触发的。
  • UserBlockingEvent用户阻塞事件,例如touchMovemouseMovescrolldragdragOver等等,这些事件会阻塞用户的交互。
  • ContinuousEvent连续事件,例如loaderrorloadStartabortanimationEnd,这个优先级最高,也就是说它们应该是立即同步执行的,这就是Continuous的意义,是持续地执行,不能被打断。

此外React将事件系统用到了Fiber架构里,Fiber中将任务分成了5大类,对应不同的优先级,那么三大类的事件系统和五大类的Fiber任务系统的对应关系如下。

  • Immediate: 此类任务会同步执行,或者说马上执行且不能中断,ContinuousEvent便属于此类。
  • UserBlocking: 此类任务一般是用户交互的结果,需要及时得到反馈,DiscreteEventUserBlockingEvent都属于此类。
  • Normal: 此类任务是应对那些不需要立即感受到反馈的任务,比如网络请求。
  • Low: 此类任务可以延后处理,但最终应该得到执行,例如分析通知。
  • Idle: 此类任务的定义为没有必要做的任务。

回到trapEventForPluginEventSystem,实际上在这三类事件,他们最终都会有统一的触发函数dispatchEvent,只不过在dispatch之前会需要进行一些特殊的处理。

// packages\react-dom\src\events\ReactDOMEventListener.js line 256
function trapEventForPluginEventSystem(element: Document | Element | Node,topLevelType: DOMTopLevelEventType,capture: boolean,
): void {let listener;switch (getEventPriority(topLevelType)) {case DiscreteEvent:listener = dispatchDiscreteEvent.bind(null,topLevelType,PLUGIN_EVENT_SYSTEM,);break;case UserBlockingEvent:listener = dispatchUserBlockingUpdate.bind(null,topLevelType,PLUGIN_EVENT_SYSTEM,);break;case ContinuousEvent:default:// 统一的分发函数 dispatchEventlistener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);break;}const rawEventName = getRawEventName(topLevelType);if (capture) {// 注册捕获事件addEventCaptureListener(element, rawEventName, listener);} else {// 注册冒泡事件addEventBubbleListener(element, rawEventName, listener);}
}

到达最终的事件注册,实际上就是在document上注册了各种事件。

// packages\react-dom\src\events\EventListener.js line 10
export function addEventBubbleListener(element: Document | Element | Node,eventType: string,listener: Function,
): void {element.addEventListener(eventType, listener, false);
}export function addEventCaptureListener(element: Document | Element | Node,eventType: string,listener: Function,
): void {element.addEventListener(eventType, listener, true);
}export function addEventCaptureListenerWithPassiveFlag(element: Document | Element | Node,eventType: string,listener: Function,passive: boolean,
): void {element.addEventListener(eventType, listener, {capture: true,passive,});
}

事件存储

让我们回到上边的listenToTopLevel方法中的listeningSet.add(topLevelType),即是将事件添加到注册到事件列表对象中,即将DOM节点和对应的事件保存到Weak Map对象中,具体来说就是DOM节点作为键名,事件对象的Set作为键值,这里的数据集合有自己的名字叫做EventPluginHub,当然在这里最理想的情况会是使用WeakMap进行存储,不支持则使用Map对象,使用WeakMap主要是考虑到WeakMaps保持了对键名所引用的对象的弱引用,不用担心内存泄漏问题,WeakMaps应用的典型场合就是DOM节点作为键名。

// packages\react-dom\src\events\ReactBrowserEventEmitter.js line 88
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const elementListeningSets:| WeakMap| Map<Document | Element | Node,Set<DOMTopLevelEventType | string>,> = new PossiblyWeakMap();export function getListeningSetForElement(element: Document | Element | Node,
): Set<DOMTopLevelEventType | string> {let listeningSet = elementListeningSets.get(element);if (listeningSet === undefined) {listeningSet = new Set();elementListeningSets.set(element, listeningSet);}return listeningSet;
}

事件合成

首先来看看handleTopLevel的逻辑,handleTopLevel主要是缓存祖先元素,避免事件触发后找不到祖先元素报错,接下来就进入runExtractedPluginEventsInBatch方法。

// packages\react-dom\src\events\ReactDOMEventListener.js line 151
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);for (let i = 0; i < bookKeeping.ancestors.length; i++) {targetInst = bookKeeping.ancestors[i];const eventTarget = getEventTarget(bookKeeping.nativeEvent);const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType);const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent);runExtractedPluginEventsInBatch(topLevelType,targetInst,nativeEvent,eventTarget,bookKeeping.eventSystemFlags,);}
}

runExtractedPluginEventsInBatchextractPluginEvents用于通过不同的插件合成事件events,而runEventsInBatch则是完成事件的触发。

// packages\legacy-events\EventPluginHub.js line 160
export function runExtractedPluginEventsInBatch(topLevelType: TopLevelType,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: EventTarget,eventSystemFlags: EventSystemFlags,
) {const events = extractPluginEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget,eventSystemFlags,);runEventsInBatch(events);
}

extractPluginEvents中遍历所有插件的extractEvents方法合成事件,如果这个插件适合于这个events则返回它,否则返回null。默认的有5种插件SimpleEventPluginEnterLeaveEventPluginChangeEventPluginSelectEventPluginBeforeInputEventPlugin

// packages\legacy-events\EventPluginHub.js line 133
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;
}

不同的事件类型会有不同的合成事件基类,然后再通过EventConstructor.getPooled生成事件,accumulateTwoPhaseDispatches用于获取事件回调函数,最终调的是getListener方法。
为了避免频繁创建和释放事件对象导致性能损耗(对象创建和垃圾回收),React使用一个事件池来负责管理事件对象(在React17中不再使用事件池机制),使用完的事件对象会放回池中,以备后续的复用,也就意味着事件处理器同步执行完后,SyntheticEvent属性就会马上被回收,不能访问了,也就是事件中的e不能用了,如果要用的话,可以通过一下两种方式:

  • 使用e.persist(),告诉React不要回收对象池,在React17依旧可以调用只是没有实际作用。
  • 使用e. nativeEvent,因为它是持久引用的。

事件分发

事件分发就是遍历找到当前元素及父元素所有绑定的事件,将所有的事件放到event._dispachListeners队列中,以备后续的执行。

// packages\legacy-events\EventPropagators.js line 47
function accumulateDirectionalDispatches(inst, phase, event) {if (__DEV__) {warningWithoutStack(inst, 'Dispatching inst must not be null');}const listener = listenerAtPhase(inst, event, phase);if (listener) {// 将提取到的绑定添加到_dispatchListeners中event._dispatchListeners = accumulateInto(event._dispatchListeners,listener,);event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);}
}

事件执行

执行事件队列用到的方法是runEventsInBatch,遍历执行executeDispatchesInOrder方法,通过executeDispatch执行调度,最终执行回调函数是通过invokeGuardedCallbackAndCatchFirstError方法。

// packages\legacy-events\EventBatching.js line 42
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();
}// packages\legacy-events\EventPluginUtils.js line 76
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;
}// packages\legacy-events\EventPluginUtils.js line 66
export function executeDispatch(event, listener, inst) {const type = event.type || 'unknown-event';event.currentTarget = getNodeFromInstance(inst);invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);event.currentTarget = null;
}// packages\shared\ReactErrorUtils.js line 67
export function invokeGuardedCallbackAndCatchFirstError<A,B,C,D,E,F,Context,
>(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,
): void {invokeGuardedCallback.apply(this, arguments);if (hasError) {const error = clearCaughtError();if (!hasRethrowError) {hasRethrowError = true;rethrowError = error;}}
}

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://zhuanlan.zhihu.com/p/53961511
https://zhuanlan.zhihu.com/p/25883536
https://zhuanlan.zhihu.com/p/140791931
https://www.jianshu.com/p/8d8f9aa4b033
https://toutiao.io/posts/28of14w/preview
https://juejin.cn/post/6844903988794671117
https://segmentfault.com/a/1190000015142568
https://zh-hans.reactjs.org/docs/events.html
https://github.com/UNDERCOVERj/tech-blog/issues/13
https://blog.csdn.net/kyooo0/article/details/111829693

React中的合成事件相关推荐

  1. [react] 怎样在react中创建一个事件?

    [react] 怎样在react中创建一个事件? var EventEmitter = require('events').EventEmitter; class App extends Compon ...

  2. [react] 在React中怎么阻止事件的默认行为?

    [react] 在React中怎么阻止事件的默认行为? event.preventDefault();阻止浏览器默认行为, 例如标签不跳转 event.stopPropagation();阻止冒泡: ...

  3. react中 onkeyPress键盘事件keyCode无效的问题

    我们都知道键盘事件有 1.onkeydown, 2.onkeyup, 3.onkeypress 三种键盘事件 对应的按下键盘上的键的时候,有keyCode, charCode两个属性. 在js中,ke ...

  4. 细说react源码中的合成事件

    React 的鲜活生命起源于 ReactDOM.render ,这个过程会为它的一生储备好很多必需品,我们顺着这个线索,一探婴儿般 React 应用诞生之初的悦然. 更新创建的操作我们总结为以下两种场 ...

  5. 在React中传递onFocus事件的参数

    <Select style={{ width: '70%', marginLeft: 5 }}disabled={!!_.recordId}mode="multiple"ma ...

  6. react 八千字长文深入了解react合成事件底层原理,原生事件中阻止冒泡是否会阻塞合成事件?

    壹 ❀ 引 在前面两篇文章中,我们花了较大的篇幅介绍react的setState方法,在介绍setState同步异步时提到,在react合成事件中react对于this.state更新都是异步,但在原 ...

  7. React 合成事件

    文章借鉴 pingan8787 React合成事件 和 React合成事件官方文档 React 合成事件 一.概念介绍 React合成事件是React 模拟原生DOM事件所有能力 的一个事件对象. 根 ...

  8. jquery 监听td点击事件_React 事件 | 1. React 中的事件委托

    说到 React 的事件,也算是 React 的一个非常有亮点的优化,它在原生事件体系的基础上,单独实现了一套事件机制.想要了解这个机制,首先的了解下什么是事件委托以及事件委托的好处. 事件委托 假设 ...

  9. 【React】合成事件和原生事件

    欢迎学习交流!!! 持续更新中- 文章目录 事件流 DOM事件流的几个阶段 React合成事件 React合成事件原理 在react中使用原生事件方法 合成事件和原生事件混合使用 响应顺序 阻止冒泡 ...

最新文章

  1. 深入理解Java Stream流水线,学到了!
  2. 通过 JS 脚本去除csdn广告
  3. C# Excel转换为Json
  4. superslide 学习笔记
  5. sqlserver 把SELECT结果集中一列的所有的值 用逗号隔开放进一个字段内
  6. Write operations are not allowed in read-only mode (FlushMode.MANUAL)
  7. HDU 3832 Earth Hour
  8. 如何摆脱工具类【转载】
  9. MIS系统权限控制的一个简便方法
  10. 服务器怎么关闭终端依然运行node,关闭控制台后如何永久运行node.js应用程序?...
  11. css 识别屏幕大小自适应
  12. 总是听到有人说AndroidX,到底什么是AndroidX?
  13. 强化学习的学习之路(四十八)2021-02-17 GAE(Generalized Advantage Estimation)
  14. Gradually Vanishing Bridge for Adversarial Domain Adaptation
  15. 工程测量计算机在线用,《用TI 图形计算器学编程》—应用篇—工程测量.pdf
  16. 计算机模拟光学图像加密,光学图像加密中级联相位恢复算法的应用
  17. 视频webm怎么转换成mp4
  18. 小程序一个简单的订单界面
  19. 【白板动画制作软件】万彩手影大师教程 | 调整整个动画时长
  20. 扫描计算机系统类型,扫描仪支持什么操作系统

热门文章

  1. hibernate4整合spring3.1出现java.lang.NoClassDefFoundError: Lorg/hibernate/cache/CacheProvider
  2. no valid Qt versions found
  3. 事务的4大特性及实现原理
  4. Css 3d轮播样式
  5. 多线程编程-工具篇-BlockingQueue
  6. NAT原理?代理服务器原理?
  7. Maven搭建springMvc+myBatis完整项目
  8. Etl之HiveSql调优(left join where的位置)
  9. CentOS6.5下安装iRedMail中需要解决的问题
  10. “.NET研究”关于C# 中的Attribute 特性