Author: Jiang Shui

preface

React provides us with a virtual event system. I did a review of the source code for how this virtual event system works and compiled the following documents for your reference.

React events explains how to provide synthesized event objects and why. The main reason is that React wants to implement a browser-wide framework. To achieve this goal, we need to provide a browser-wide consistent event system to smooth out differences between browsers.

SyntheticEvent is a React event synthesized using a native event. For example, onClick was synthesized using a native click event. The onMouseLeave event is synthesized using the native Mouseout event. The native event and the synthesized event type are mostly one-to-one, and we only need to use the non-corresponding event composition when compatibility issues are involved. React is not the first application to synthesize events. Fastclick, introduced in iOS due to 300ms of problems, uses touch events to synthesize click events. It is also an application to synthesize events.

React events can be viewed differently when we understand that they are composite events, such as those we often write in code

<button onClick={handleClick}>
  Activate Lasers
</button>
Copy the code

We already know that onClick is a composite event and not a native event, so what happened during that time? How do native events and composite events correspond?

The React code looks simple, but the React system is much more complex. Its working principle is generally divided into two stages

  1. event
  2. Events trigger

Here’s a look at how the two phases work, mainly from the source layer, with 16.13 source code as the benchmark.

1. How does React bind events?

React provides synthesized events, so you need to know how synthesized events correspond to native events. This correspondence is stored in the React EventPlugin. The EventPlugin can be considered as a module that encapsulates different synthesized event handlers. Each module handles only its own composite events, so that different types of events can be decoupled from the code. For example, there is a separate LegacyChangeEventPlugin for onChange events, and for onMouseEnter, OnMouseLeave LegacyEnterLeaveEventPlugin plug-ins to deal with.

In order to understand the relationship between synthesized events and native events, React loads all the event plug-ins at the beginning. This logic is shown in the ReactDOMClientInjection code below

injectEventPluginsByName({
    SimpleEventPlugin: LegacySimpleEventPlugin,
    EnterLeaveEventPlugin: LegacyEnterLeaveEventPlugin,
    ChangeEventPlugin: LegacyChangeEventPlugin,
    SelectEventPlugin: LegacySelectEventPlugin,
    BeforeInputEventPlugin: LegacyBeforeInputEventPlugin
});
Copy the code

After registering the above plug-ins, the EventPluginRegistry module (called EventPluginHub in older versions of the code) initializes some global objects, but a few of the more important ones can be highlighted separately.

The first object, registrationNameModule, contains a mapping of the React event to its corresponding plugin. It looks like this. It contains all the event types supported by React. The main purpose of this object is to determine whether a component prop is an event type. This is used when dealing with props of native components. If a prop is in this object, it is treated as an event.

{
    onBlur: SimpleEventPlugin,
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    onChangeCapture: ChangeEventPlugin,
    onMouseEnter: EnterLeaveEventPlugin,
    onMouseLeave: EnterLeaveEventPlugin,
    ...
}
Copy the code

The second object is registrationNameDependencies, following the object looks like

{
    onBlur: ['blur'].onClick: ['click'].onClickCapture: ['click'].onChange: ['blur'.'change'.'click'.'focus'.'input'.'keydown'.'keyup'.'selectionchange'].onMouseEnter: ['mouseout'.'mouseover'].onMouseLeave: ['mouseout'.'mouseover'],... }Copy the code

This object is the mapping from the synthesized event to the native event we talked about in the beginning. For onClick and onClickCapture events, only the native Click event is relied on. For onMouseLeave it relies on two mouseOuts, mouseover, which means React uses mouseout and mouseover to simulate the event. It’s this behavior that allows React to synthesize events for use in our code, even if the browser doesn’t support them.

The third object is plugins, which are a list of all the plug-ins registered above.

plugins = [LegacySimpleEventPlugin, LegacyEnterLeaveEventPlugin, ...] ;Copy the code

Let’s take a look at what a normal EventPlugin might look like. A plugin is an object that contains the following two properties

// event plugin
{
  eventTypes, // An array containing information about all synthesized events, including their corresponding native event relationships
  extractEvents: // a function that executes when a native event is emitted
}
Copy the code

React events are bound to React events. React events are bound to React events.

  1. React performs the diff operation to mark which DOM type nodes need to be added or updated.

  1. When it detects that a node needs to be created or updated, use registrationNameModule to check if a prop is an event type and proceed to the next step if it is.

  1. Through registrationNameDependencies check this React event depends on what native event type.

  1. Check if one or more of these native event types are registered and ignore them if so.

  1. If the native event type is not already registered, the native event type is registered todocumentThe callback is provided for ReactdispatchEventFunction.

The stages above illustrate:

  1. We registered all event types todocumentOn.
  2. The listener for all native events isdispatchEventFunction.
  3. React will only bind native events once for the same type of event, for example, no matter how many we writeonClickIn the end, there will only be one reaction to the DOM eventlistener.
  4. React does not incorporate the elements in our business logiclistenerTied to native events, there is no attempt to maintain a similareventlistenermapThings to store ourslistener.

According to 3 or 4 rules, the listener of our business logic has nothing to do with the actual DOM event. React only ensures that the native event is captured by itself. React then dispatches our event callback. React doesn’t have to do anything to remove removeEventListener or synchronize EventListenerMap, so it’s much more efficient. It’s like doing a global event delegate, and you don’t have to worry about event binding even if you’re rendering a large list.

2. How does React trigger events?

We know that since all types of events are bound to the React dispatchEvent function, we can handle some common behaviors globally. Here’s how this happens.

export function dispatchEventForLegacyPluginEventSystem(
  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); }}Copy the code

BookKeeping stores the hierarchy of components at the time of event execution, meaning that if a component structure changes during event execution, the event firing process will not be affected.

The whole triggering event flow is as follows:

  1. If any event is triggered, executedispatchEventFunction.
  2. dispatchEventperformbatchedEventUpdates(handleTopLevel).batchedEventUpdatesThe batch render switch is turned on and calledhandleTopLevel.
  3. HandleTopLevel executes all the event plug-ins in the plugins in sequence.
  4. If a plug-in detects an event type that it needs to handle, it handles that event.

The processing logic for most events is as follows, which is what the LegacySimpleEventPlugin does

  1. Which synthetic event type to use is determined by the native event type (the wrapper object of the native event, for exampleSyntheticMouseEvent).
  2. If there is an instance of this type in the object pool, the instance will be taken out and its properties overwritten as the event object (event object reuse) distributed this time. If there is no instance, a new instance will be created.

  1. Find the corresponding DOM node from the native event clicked, find the most recent React component instance from the DOM node, and find an upward chain of the instance’s parent node. This chain is the chain that triggers the composite event.div.aThis native component).

  1. Trigger the chain in reverse, parent -> child, simulate the capture phase, trigger all props containonClickCaptureThe instance.

  1. Forward trigger this chain, child -> parent, simulate the bubbling phase, trigger all props containonClickThe instance.

These stages illustrate the following phenomena:

  1. React synthesized events can only be used during the event cycle because the object is likely to be reused by other phases and will need to be called manually to persistevent.persist()Tell React that this object needs to be persisted. (Deprecated in React17)
  2. React bubbling and capturing isn’t really DOM level bubbling and capturing
  3. React triggers all related nodes in a native eventonClickEvents in the execution of theseonClickPreviously React would turn on the batch render switch, which would turn all of thesetStateBecome an asynchronous function.
  4. Events are only valid for native components, not custom componentsonClick.

3. What have we learned from the React event system

  1. React16 binds native events todocumentOn.

This makes sense. React events are actually triggered on document.

  1. The event object we receive is a React composite event. The event object cannot be used outside of the event

So here are the wrong ones

function onClick(event) {
    setTimeout(() = > {
        console.log(event.target.value); },100);
}
Copy the code
  1. React will turn on batch updates when it dispatches eventssetStateIt’s going to be asynchronous.
function onClick(event) {
    setState({a: 1}); / / 1
    setState({a: 2}); / / 2
    setTimeout(() = > {
        setState({a: 3}); / / 3
        setState({a: 4}); / / 4},0);
}
Copy the code

In this case, 1 and 2 are asynchronous within the event and only trigger the render operation once, 3 and 4 are synchronous, and 3 and 4 each trigger the render operation once.

  1. React onClick/onClickCaptureIn fact, all occur during the bubbling phase of the native event.
document.addEventListener('click'.console.log.bind(null.'native'));

function onClickCapture() {
    console.log('capture');
}

<div onClickCapture={onClickCapture}/>
Copy the code

Here we use onClickCapture, but it’s still bubbling for native events, so React 16 doesn’t actually support binding to capture events.

  1. Since all events are registered to the top-level event, there are many real eventsReactDOM.renderThere will be conflicts.

If we render a subtree created using a different version of React instance, the E.sppropagatio event will propagate even if an E.sppropagatio call is made in the subtree. Therefore, multiple React versions have conflicting events.

Finally, we can easily understand the React event system architecture diagram

What are the new features of the React 17 event system

React 17 is out now, officially billed as a features-free update. There are no Hooks explosions for users, no major refactoring like Fiber, but a stack of bugfixes that fix a lot of previous bugs. One of the biggest changes is the transformation of the event system.

Here are some of the event-related feature updates I’ve listed

Reactdom.render (app, container);

Binding top-level events to Containers instead of documents solves the problem of multi-version coexistence that we encountered, which is a big plus for microfront-end solutions.

Align native browser events

React 17 finally supports native event capture, aligning with browser native standards.

At the same time, the onScroll event no longer bubbles.

OnFocus and onBlur use native FocusIn, focusOut compositing.

Aligning with Browsers

We’ve made a couple of smaller changes related to the event system: The onScroll event no longer bubbles to prevent common confusion. React onFocus and onBlur events have switched to using The native focusin and focusout events under the hood, Capture phase events which more closely match React’s existing behavior and sometimes provide extra information onClickCapture) now use real browser capture phase listeners.

Cancel event reuse

The official explanation is that the reuse of event objects has not improved significantly in modern browsers, and it is still easy to use incorrectly, so this optimization should be abandoned altogether.

reference

  1. Reactjs.org/docs/events…
  2. Reactjs.org/docs/handli…
  3. Github.com/facebook/re…

This article is published from netease Cloud Music big front end team, the article is prohibited to be reproduced in any form without authorization. Grp.music – Fe (at) Corp.Netease.com We recruit front-end, iOS and Android all year long. If you are ready to change your job and you like cloud music, join us!