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
- event
- 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.
- React performs the diff operation to mark which DOM type nodes need to be added or updated.
- 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.
- Through registrationNameDependencies check this React event depends on what native event type.
- Check if one or more of these native event types are registered and ignore them if so.
- If the native event type is not already registered, the native event type is registered to
document
The callback is provided for ReactdispatchEventFunction.
The stages above illustrate:
- We registered all event types to
document
On. - The listener for all native events is
dispatchEvent
Function. - React will only bind native events once for the same type of event, for example, no matter how many we write
onClick
In the end, there will only be one reaction to the DOM eventlistener
. - React does not incorporate the elements in our business logic
listener
Tied to native events, there is no attempt to maintain a similareventlistenermap
Things 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:
- If any event is triggered, execute
dispatchEvent
Function. dispatchEvent
performbatchedEventUpdates(handleTopLevel)
.batchedEventUpdatesThe batch render switch is turned on and calledhandleTopLevel
.- HandleTopLevel executes all the event plug-ins in the plugins in sequence.
- 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
- Which synthetic event type to use is determined by the native event type (the wrapper object of the native event, for example
SyntheticMouseEvent
). - 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.
- 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
.a
This native component).
- Trigger the chain in reverse, parent -> child, simulate the capture phase, trigger all props contain
onClickCapture
The instance.
- Forward trigger this chain, child -> parent, simulate the bubbling phase, trigger all props contain
onClick
The instance.
These stages illustrate the following phenomena:
- 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 persist
event.persist()
Tell React that this object needs to be persisted. (Deprecated in React17) - React bubbling and capturing isn’t really DOM level bubbling and capturing
- React triggers all related nodes in a native event
onClick
Events in the execution of theseonClick
Previously React would turn on the batch render switch, which would turn all of thesetState
Become an asynchronous function. - Events are only valid for native components, not custom components
onClick
.
3. What have we learned from the React event system
- React16 binds native events to
document
On.
This makes sense. React events are actually triggered on document.
- 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
- React will turn on batch updates when it dispatches events
setState
It’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.
- React
onClick
/onClickCapture
In 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.
- Since all events are registered to the top-level event, there are many real events
ReactDOM.render
There 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
- Reactjs.org/docs/events…
- Reactjs.org/docs/handli…
- 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!