preface
React has a unique event mechanism called compositing events. Beginners of React will encounter this problem. Use event.stopPropagation(). However, there is no way to prevent events from bubbling from the current component. This is why React’s event mechanism is not the same as DOM events.
The React event system is implemented in the first place.
The DOM event flow
DOM event flow is a relatively basic knowledge point, this article will not be described in detail, only listed some key points needed. The W3C standard states that the propagation of an event goes through three phases:
- 1. Event capture phase
- 2. Goal stage
- 3. Event bubbling phase
With DOM event flow, we often use an old performance optimization idea: event delegation.
The React event system is also based on the event delegate feature.
React Event System
React events are not bound to specific elements, except for non-bubbling events. Instead, they are bound to document (after version 17, they are bound to the root DOM component of React). When events are triggered on specific DOM nodes, they eventually bubble to document. The React root component binds to a unified event handler that dispatches events to specific component instances.
React first wraps events before distributing them, wrapping native DOM events as composite events.
React synthesis event
React is a custom event object that smooths out browser differences at the bottom and exposes developers to a unified, stable event interface that is identical to DOM native events at the top.
Although the synthesized event is not a native DOM event, it retains a reference to the native DOM event. When you need to access native DOM event objects, you can get native DOM events from the synthesized event object’s E.ativeEvent property.
React event binding
The binding of events is done in the completeWork method of the component’s first render link. For the first rendering process, see my previous article
How do you render the React code into the DOM?
CompleteWork does three main things:
- Create a DOM node (createInstance)
- Insert the DOM node into the DOM tree (appendAllChildren)
- Set the property (finalizeInitialChildren) for the DOM node.
In the finalizeInitialChildren method, the props of the node are iterated. When traversing the functions associated with the event, the registration link for the event is triggered.
This article is based on the React source code version 16.13. Note: version 17 of the event system in the source code changes are relatively large, the source link is not consistent with this article.
In ensureListeningTo, the Document object in the current DOM is retrieved, and the unified event-listening function is registered on the Document by calling legacyListenToEvent.
In legacyListenToEvent, is, in fact, by calling the legacyListenToTopLevelEvent to handle the relationship between the event and the document. LegacyListenToTopLevelEvent literal translation is “listening top event”, the “top” can be understood as the top event delegation, which is the document node.
Note: In version 17, the process does not existensureListeningTo
andlegacyListenToEvent
Method,React
Will be infinalizeInitialChildren
Methods under thesetInitialProperties
According to the nodetag
Type, passed in different arguments and calledlistenToNonDelegatedEvent
Methods. In this method, it’s called directlyaddTrappedEventListener
Add events toReact
The root of the componentDOM
Element.
ListenToNonDelegatedEvent source address
What is registered with the Document is not the specific callback logic corresponding to a DOM node, but a unified event distribution function listener whose ontology is a dispatchEvent.
Trigger the React event
Event triggering is simply a call to the dispatchEvent function.
Let’s go through the process based on the following demo
import React from 'react';
import { useState } from 'react'
function App() {
const [state, setState] = useState(0);
return (
<div className="App">
<div
className="container"
onClickCapture={()= >Console. log(' captured by div')} onClick={() => console.log(' bubbled by div')} ><p>{state}</p>
<button onClick={()= >{setState(state +1)}}> Click +1</button>
</div>
</div>
);
}
export default App;
Copy the code
The demo is very simple. Every time you click the button, you increment state by 1. Add two click events to the Container div, one capture and one bubble. Below is the fiber tree structure of the demo.
Collect the logical process in the traverseTwoPhase function
function traverseTwoPhase(inst, fn, arg) {
// Define a path array
var path = [];
while (inst) {
// Collect the current node into the PATH array
path.push(inst);
// Collect tag=== parent of HostComponent upward
inst = getParent(inst);
}
var i;
// From back to front, collect the nodes in the PATH array that will participate in the capture process and the corresponding callback
for (i = path.length; i-- > 0;) {
fn(path[i], 'captured', arg);
}
From front to back, collect the nodes in the PATH array that will participate in the bubbling process and the corresponding callbacks
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg); }}Copy the code
The traverseTwoPhase function does three things.
- Loop through the collection of eligible parent nodes and store them in the PATH array
- Simulate the propagation order of events in the capture phase and collect node instances and callback functions related to the capture phase
- Simulate the propagation order of events in the bubbling phase, collect the node instances and callback functions related to the bubbling phase
Collect parent node
The traverseTwoPhase starts at the target node that triggered the event, uses the getParent method to find the parent nodes of the Tag ===HostComponent, and collects these nodes in sequence into the PATH array. The tag===HostComponent node is the fiber node type corresponding to the DOM element, that is, only the DOM element corresponding node is collected.
According to the Fiber tree in the demo, the last nodes collected are Div #container, div.App and button node.
Simulate the capture sequence, collecting node instances and callback functions
for (i = path.length; i-- > 0;) {
fn(path[i], 'captured', arg);
}
Copy the code
The PATH array is collected from the child nodes and up. So, to simulate the capture order of events, you need to traverse the PATH array from back to front. In the traverse process, fn function detects the event callback of each node. If the capture callback corresponding to the current event on this node is not empty, then the node Fiber instance will be collected into the SYNtheticEvent._DispatchinSTANCES stances. Event callbacks are collected on the syntheticevent._dispatchlisteners property of the SyntheticEvent.
Simulate bubbling sequence, collect node instances and callback functions
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
Copy the code
The functionality is the same as in the previous step, except that the path array is iterated from before to after.
Finally, let’s take a lookSyntheticEvent
On the object_dispatchInstances
and_dispatchListeners
.
We can simulate the flow of DOM events, the catch-target-bubble phases, by simply calling the execution callback in sequence.
Update to the event system in Act 17
The above source code analysis is based on 16.13.x version, 17 version after the event system, there is a big difference.
- 1. The event system is mounted to
React
On the root component DOM - 2.
onScroll
Events no longer bubble up - 3.
onFocus
andonBlur
Events have been switched to native at the bottomfocusin
andfocusout
The event - 4.
onClickCapture
You now use a capture listener in the actual browser (the composite event only existslistenToNonDelegatedEvent
Added bubbling event) - 5. Events pool
SyntheticEvent
No longer reusable, click events can be retrieved using asynchronous methods in click events. You don’t need to use it anymoree.persist()
methods
Thank you
If this article helped you, please give it a thumbs up. Thanks!