Writing in the front
Last month, I had the honor to study the knowledge point of react event mechanism, and shared my understanding in the company. Now while it is still hot to quickly finish down, retain this long face moment.
The outline
The react event mechanism is analyzed based on the source code. It is expected to give you a clearer understanding of the React event mechanism.
Of course, there will certainly be some unclear or not enough standard understanding of the place, but also ask you to correct god, big guy.
01 – Preliminary understanding and validation of the event mechanism
02 – Understanding of composition
03 – Event registration mechanism
04 – Event execution mechanism
01 and 02 are theoretical nonsense, which is also my personal summary. If you are not interested, you can jump to 03- event execution mechanism.
Ps: This article is based on React15.6.1. Although it is not the latest version, it does not affect our overall understanding of the React event mechanism.
Preliminary understanding and validation of event mechanisms
Representational understanding, validation, meaning and reflection on react event mechanism.
Image understanding
React implements its own event mechanism, including event registration, event composition, event bubbling, event dispatch, etc. Although it is different from the original event mechanism, it is also implemented under the browser-based event mechanism.
We all know that react events are not bound to a specific DOM node, but to a Document, which is then handled by a unified event handler, which is also browser-based event mechanism (bubbling). All node events are triggered on the Document.
Now that we have a basic understanding of react events, is this understanding correct? We can verify this in a simple way.
validation
Verification content:
All events are registered to the top-level document of the element. Events on the node are handled by a unified entry. For convenience, a project is created directly through the CLI.
componentDidMount(){
document.getElementById('btn-reactandnative').addEventListener('click'.(e) = > {
console.log('Native + React Events: Native Event execution');
});
}
handleNativeAndReact = (e) = > {
console.log('Native + React event: the react event is currently executed');
}
handleClick=(e) = >{
console.log('button click');
}
render(){
return <div className="pageIndex"><p>react event!!! </p<button id="btn-confirm" onClick={this.handleClick}>The react event</button>
<button id="btn-reactandnative" onClick={this.handleNativeAndReact}>Native + React event</button>
</div>
}
Copy the code
The code binds two buttons to a composite event and BTN # bTN-reactandNative to a single native event.
Then look at the Chrome console to see the events registered on the element.
After simple verification, we can see that all events are bound to document according to different event types, and the triggering function is dispatchEvent.
imagine
If both synthetic and native events are bound on a node, what about a no-bubbling relationship?
The answer is already there. Let’s now analyze this relationship based on what we know so far.
Because composite events are triggered based on the browser’s event mechanism, they bubble up to the top-level element through the bubbling mechanism, which is then handled by dispatchEvent.
** conclusions: **
Native event preventing bubbling definitely prevents synthetic events from firing.
Preventing bubbling for composite events does not affect native events.
Why is that? Remember the browser event mechanism
Browser events are executed through three phases: capture phase – target element phase – bubbling phase.
Native events on the node are executed in the target phase, whereas synthesized events are executed in the bubbling phase, so native events are synthesized first and then bubbled to the parent node.
Since the native prevent bubbling, then the composition of the implementation of what.
Ok, it’s the turn of the composition to be prevented from bubbling, will the native be implemented? Of course I can.
Because the native event precedes the execution of the composite, only the composite event is prevented from bubbling within the composite event. (I won’t post the code)
So the conclusion is drawn:
Native events (which prevent bubbling) prevent synthetic events from executing
Synthetic events (which prevent bubbling) do not prevent native events from executing
It is best not to mix the two to avoid some strange problems
meaning
What’s the point of react doing so much by itself?
-
Reduce memory consumption, improve performance, don’t need to register so many events, an event type is registered on document only once
-
Unified specification, solve IE event compatibility problem, simplify event logic
-
Be developer friendly
thinking
Since React does so much for us, what’s the mechanics behind it?
How are events registered, how are events triggered, and how is the bubbling mechanism implemented?
Please continue to read……
Understanding of composition
When I first heard the word synthesis, I felt it was very lofty and profound, not very easy to understand.
After I had a general understanding of react event mechanism, I felt that synthesis is not only the synthesis and processing of events, but also includes:
-
Encapsulation of native events
-
Upgrades and modifications to certain native events
-
Compatible handling of events across browsers
Encapsulation of native events
The above code is a callback method that adds a click event to an element. The parameter e in the method is actually a react wrapped object instead of a native event object. Meanwhile, the native event object is placed in the property E.ativeEvent.
To debug, see what properties e contains in the execution stack
Take a look at the official documentation
SyntheticEvent is the base class for react synthesized events. It defines the basic common properties and methods for synthesized events.
React uses different synthesized event objects based on the current event type, such as single-mouse event-SyntheticMouseEvent and focus event-SyntheticFocusEvent, which are inherited from SyntheticEvent.
Upgrades and modifications to native events
For some DOM element events, react does not only handle the event types you declare, but also adds additional events to help improve the interaction experience.
Here is an example to illustrate:
When we declare an onChange event to the input, what does react do for us?
You can see react registers not just one onchange event, but many other events as well.
When we type content into the text box, we can get the content in real time.
However, native-only registering an onchange event requires that the event be triggered when the focus is out of focus, so react also helps us compensate for the drawback of the native-only event.
Ps: There is an invalid event in the red arrow above, which is not registered on the document, but on the concrete element. This is a new event in HTML5, which is automatically triggered when the input data does not conform to the validation rules. However, the validation rules and configuration are written on the current input element, and this event is invalid if registered to the document.
Compatible handling of browser events
React also handles compatibility when registering the Document event.
The above code is to register the document event, the internal is also to do the COMPATIBILITY of ie browser to do the processing.
This is my understanding of the term React composition. In fact, REACT has a lot of internal processes. I just give a few examples, and then start to talk about the mechanism of event registration and event distribution.
Event registration mechanism
This is section 3 of the React event mechanism — Event registration. Learn about the react event registration process and the key steps involved in the process, as well as the source code for validation and understanding.
I will not say very details here, but introduce the general process and the original content, so as to have a cognition and understanding of the overall process.
A flowchart
The React event registration process does two things: event registration and event storage.
A. Event registration – component mount stage, according to the declared event type within the component -onclick, onchange, etc., add event -addEventListener to document, and specify a unified event handler dispatchEvent.
B. Event storage – All events in the React component are stored in one object and cached so that the corresponding method can be found and executed when the event is triggered.
Key steps
There are two goals that event registration needs to accomplish, but what are the key processes that go through to accomplish these goals?
React gets the virtual DOM of the component to mount (which is actually the React Element object), and then handles the React DOM props to determine if any properties in the properties are declared as events. For example, onClick,onChange, get the event type click,change and the corresponding event handler fn, and then perform the next three steps
A. Complete event registration
B. Store the react DOM, event type, and handler fn in an array
C. After the component is mounted, process the array generated in step B and iterate through the event handler to the listenerBank(an object)
The source code parsing
From the JSX
Let’s take a look at the code that we’re most familiar with, the way we write it everyday
// the code is omitted here
handleFatherClick=() = >{
}
handleChildClick=() = >{}render(){
return <div className="box">
<div className="father" onClick={this.handleFatherClick}>
<div className="child" onClick={this.handleChildClick}>child </div>
</div>
</div>
}
Copy the code
After compiling with Babel, you can see that the final method called is react.createElement,z and the declared event type and callback are props.
The result of the react. CreateElement execution returns what is called a virtual DOM (React Element Object).
Handle component props, get the event type and callback FN
The ReactDOMComponent needs to handle props (_updateDOMProperties) when performing component loading (mountComponent) and updating (updateComponent) :
Take a look at registrationNameModules and, without going into details, it is a built-in constant.
Event registration and event storage
Event registration
The code above then executes the method
enqueuePutListener(this, propKey, nextProp, transaction);
Copy the code
In this method, events are registered and stored, including bubbling and capturing
Get the highest parent – document – based on the current component instance, and then execute the method listenTo – which is also the most critical method for event binding processing.
Source code files: ReactBrowerEventEmitter. Js
Eventlistener. listen(bubble) or eventListener. capture (capture).
You can also see that the event registration is also compatible with Internet Explorer.
Instead of seeing the definition of dispatchEvent above, you can see the code passing in the dispatchEvent method below.
Register the event here and you’re done.
The event store
React stores all events via dispatchEvent instead of registering the declared callback.
React associates all events with event types and the React component. This relationship is stored in a map, that is, an object (key-value pairs). Then when the event is triggered, it will find the corresponding event FN based on the current component ID and event type.
Combined source code:
function enqueuePutListener(inst, registrationName, listener, transaction) {
var containerInfo = inst._hostContainerInfo;
var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
listenTo(registrationName, doc);// This method is finished above
// Transactions are involved here, which will be covered in a later section, focusing on event registration
// The following code puts putListener into an array. When the component finishes mounting, the array callback will be executed in turn. Putlisteners are executed in sequence
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,// Component instance
registrationName: registrationName,// Event type click
listener: listener // Event callback fn
});
}
function putListener() {
var listenerToPut = this;
// Put into array, callback queue
EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener);
}
Copy the code
ListenTo (event registration) is executed, and putListener method is executed for event storage. All events are stored in an object – listenerBank, which is managed by EventPluginHub.
// Get the unique component id
var getDictionaryKey = function getDictionaryKey(inst) {
return '. ' + inst._rootNodeID;
}
putListener: function putListener(inst, registrationName, listener) {
// Get the component ID
var key = getDictionaryKey(inst);
// Get the object of the specified event type in the listenerBank object
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
// Store callback fn
bankForRegistrationName[key] = listener;
//....
}
Copy the code
ListenerBank is actually a two-level map, which makes it easier to find events.
The component ID is the unique identifier of the component, which is then associated with fn to find the related event callback during the firing phase.
Does this look familiar to you? This is the object that we normally use.
Here the general flow has said that finish, is not the feeling a bit clear and not quite clear.
It doesn’t matter. Let’s do a more detailed picture, just to re-understand it.
Event execution mechanism
In the event registration phase, eventually all events and event types are saved to listenerBank.
So what’s the use of this object in the event firing process?
It’s used to find event callbacks
A flowchart
The event triggering process is summarized as the following steps:
1. Enter unified dispatchEvent function
2. Combine native events to find the ReactDOMComponent object corresponding to the current node
3. Start composing events
3.1 Generate the specified composite object based on the current event type
3.2 Encapsulate native event and bubbling mechanisms
3.3 Find the current element and all its parents
3.4 Find event callback in listenerBank and synthesize to Event (synthesize event end)
4. Batch processing of callback events within composite events (event trigger completion end)
Take a chestnut
Before going into the process, let’s take a look at the chestnut on which the rest of the analysis is based
handleFatherClick=(e) = >{
console.log('father click');
}
handleChildClick=(e) = >{
console.log('child click');
}
render(){
return <div className="box">
<div className="father" onClick={this.handleFatherClick}> father
<div className="child" onClick={this.handleChildClick}>child </div>
</div>
</div>
}
Copy the code
Looking at this familiar code, we already know the result of the execution.
When I click on child div, it also triggers the father event.
The source code parsing
DispatchEvent Dispatches events
Enter the unified dispatchEvent function.
When I click on the Child div, that’s when the browser catches the event, and it bubbles up, and it bubbles up to the document, and it gets handled by the unified event handler dispatchEvent. As we said in the previous article, a unified event handler called dispatchEvent is registered on document.
Find ReactDOMComponent
Find the ReactDOMComponent object that corresponds to the current node with the native event. A reference to the ReactDOMComponent instance is already stored in the native event object, presumably at mount time.
Take a look at the contents of the ReactDOMComponent instance
Event composition ING
The composition of events, bubbling handling, and lookup of event callbacks are all done in the composition phase.
Generation of synthetic objects
Find the corresponding composite class based on the current event type, and then generate the composite object
// Perform event composition to get the specified composite class based on the event type
var SimpleEventPlugin = {
eventTypes: eventTypes,
extractEvents: function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
// The code has been omitted....
var EventConstructor;
switch (topLevelType) {
// The code has been omitted....
case 'topClick':TopLevelType = topClick; topLevelType = topClick
if (nativeEvent.button === 2) {
return null;
}
// The code has been omitted....
case 'topContextMenu':// Instead, it will execute here and get the mouse composite class
EventConstructor = SyntheticMouseEvent;
break;
case 'topAnimationEnd':
case 'topAnimationIteration':
case 'topAnimationStart':
EventConstructor = SyntheticAnimationEvent;// The animation class synthesizes events
break;
case 'topWheel':
EventConstructor = SyntheticWheelEvent;// The mouse wheel class synthesizes events
break;
case 'topCopy':
case 'topCut':
case 'topPaste':
EventConstructor = SyntheticClipboardEvent;
break;
}
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;// The resultant event object is eventually returned
}
Copy the code
Encapsulate native event and bubbling mechanisms
In this step, the native event object is attached to the synthesized object itself, and the default behavior handling and bubbling mechanisms for the event are added
/ * * * *@param {obj} DispatchConfig A configuration object contains the current event dependency ["topClick"], bubbled: "onClick",captured: "onClickCapture" *@param {obj} TargetInst component instance ReactDomComponent *@param {obj} NativeEvent nativeEvent object *@param {obj} NativeEventTarget Event source e.target = div.child */
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;// Save the native object to this.nativeEvent
// The code here is omitted.....
vardefaultPrevented = nativeEvent.defaultPrevented ! =null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
// The default behavior for handling events
if (defaultPrevented) {
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
} else {
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
}
ThatReturnsFalse returns false by default,that is, does not prevent bubbling
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
return this;
}
Copy the code
The following is how the added default behavior and bubbling mechanism are handled by changing the property value of the current synthesized object. If the property value is true when the method is called, the default behavior or bubbling will be prevented.
// Add preventDefault and stopPropagation methods to the synthetic prototype
_assign(SyntheticEvent.prototype, {
preventDefault: function preventDefault() {
/ /... slightly
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
},
stopPropagation: function stopPropagation() {
/ /... slightly
this.isPropagationStopped = emptyFunction.thatReturnsTrue; });Copy the code
Take a look at the emptyFunction code
Find all parent instances
Locate all parent instances of the node based on the current node instance and store them in path
/ * * * *@param {obj} Inst Indicates the current node instance *@param {function} Fn processing method *@param {obj} Arg synthesizes the event object */
function traverseTwoPhase(inst, fn, arg) {
var path = [];// Store all instances of ReactDOMComponent
while (inst) {
path.push(inst);
inst = inst._hostParent;// Hierarchy
}
var i;
for (i = path.length; i-- > 0;) {
fn(path[i], 'captured', arg);// Handle the capture and reverse the array
}
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);// Start from 0, let's look at bubbles}}Copy the code
Let’s see what path looks like
End of event composition
Look for event callbacks in listenerBank and compose them into events.
And then the code above
fn(path[i], 'bubbled', arg);
Copy the code
The above code calls the method below to find the event callback in listenerBank and store it in the composite event object.
/** eventpropagators.js * After looking for the event callback, save the instance and callback into the synthesized object *@param {obj} Inst component instance *@param {string} Phase Event type *@param {obj} Event Synthesizes the event object */
function accumulateDirectionalDispatches(inst, phase, event) {
var listener = listenerAtPhase(inst, event, phase);
if (listener) {// If the event callback is found, it is saved (stored in the synthesized event object)
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);// Merge the event callback to return a new array
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);// Merge component instances to return a new array}}/** * EventPropagators. Js * Call the instance callback method *@param {obj} Inst instance *@param {obj} Event Synthesizes the event object *@param {string} 5, propagationPhase name, capture or bubbled */
function listenerAtPhase(inst, event, propagationPhase) {
var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
/** eventPluginhub.js * Get the instance callback method *@param {obj} Inst component instance *@param {string} registrationName Name of listener (e.g. `onClick`).
* @return {? function} Return the callback method */
getListener: function getListener(inst, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
return null;
}
var key = getDictionaryKey(inst);
return bankForRegistrationName && bankForRegistrationName[key];
}
Copy the code
How can we find it?
Because there is a _rootNodeID in the INST (component instance), there is a correspondence.
At this point the event composite object is generated, and all the event callbacks are saved to the composite object.
Batch process event composite objects
Batch processing of callback methods within synthesized event objects (event triggers completion end).
After generating the composite event object, the call stack goes back to the method we originally executed.
// Perform the event callback here
runEventQueueInBatch(events);
Copy the code
Some code has been omitted and only the main code has been posted until the following step, which loops through the callback methods within the synthesized event and determines whether to disable event bubbling.
Paste the last code to execute the callback method
/ * * * *@param {obj} Event Synthesizes the event object *@param {boolean} simulated false
* @param {fn} Listener Event callback *@param {obj} Inst component instance */
function executeDispatch(event, simulated, listener, inst) {
var type = event.type || 'unknown-event';
event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
if (simulated) {// The debug environment value is false, and the production environment is normally true
// For the content of the method, see below
ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
} else {
// For the content of the method, see below
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}
event.currentTarget = null;
}
/** ReactErrorUtils.js
* @param {String} name of the guard to use for logging or debugging
* @param {Function} func The function to invoke
* @param {*} a First argument
* @param {*} b Second argument
*/
var caughtError = null;
function invokeGuardedCallback(name, func, a) {
try {
func(a);// Execute the callback method directly
} catch (x) {
if (caughtError === null) { caughtError = x; }}}var ReactErrorUtils = {
invokeGuardedCallback: invokeGuardedCallback,
invokeGuardedCallbackWithCatch: invokeGuardedCallback,
rethrowCaughtError: function rethrowCaughtError() {
if (caughtError) {
var error = caughtError;
caughtError = null;
throwerror; }}};if(process.env.NODE_ENV ! = ='production') {// Non-production environments trigger callbacks through custom events
if (typeof window! = ='undefined' && typeof window.dispatchEvent === 'function' && typeof document! = ='undefined' && typeof document.createEvent === 'function') {
var fakeNode = document.createElement('react');
ReactErrorUtils.invokeGuardedCallback = function (name, func, a) {
var boundFunc = func.bind(null, a);
var evtType = 'react-' + name;
fakeNode.addEventListener(evtType, boundFunc, false);
var evt = document.createEvent('Event');
evt.initEvent(evtType, false.false);
fakeNode.dispatchEvent(evt);
fakeNode.removeEventListener(evtType, boundFunc, false); }; }}Copy the code
React generates a temporary node fakeNode, binds the Event handler to the temporary element, creates a custom Event, triggers the Event with the fakenode.dispatchEvent method, and removes the listener Event immediately after the Event is triggered.
At this point the event callback has been executed, but there is some question as to why a custom event is needed to execute the callback method in a non-production environment. May have a look. The above code in a non-production environment on ReactErrorUtils rewrites the invokeGuardedCallback method.
conclusion
Mainly from the overall process, this paper introduces the principle of the react under event which did not go deep into the source of all details, include details of the transaction, synthetic, etc., and other places they also have some doubts in the process of combing, feeling said principle can more easily understand some, but a combination of source to write feel mess, because the react code is too big, And tangled, it is difficult to pull away, interested in the source of the inclusion can be in-depth study, of course, still hope that this article can bring you some inspiration, if the article is unclear or there are problems in the place welcome to leave a message, exchange, as if.
The last
It’s sloppy and unsightly, and you guys are suffering. ^_^…
The resources
zhuanlan.zhihu.com/p/35468208
React.docschina.org/docs/events…
Please stop here
- I am creating a pure technical exchange group, with the goal of learning, communicating, thinking and improving ability. Because learning alone is not as good as learning together, and only with more communication can we make progress faster.
- My ideal model is to let one person learn one technology in depth in each period, and then relay it to everyone, which is similar to a sharing class, so that learning efficiency can be improved exponentially
- In this group, you don’t need to worry about your lack of ability or whether the problem is too small to say. You can speak out boldly and let more people analyze it together. It doesn’t matter if you make mistakes
- Want to join the group I wechat 223344386 reply to add group
Hope this article can give you some help, if there are any mistakes in the article, feel free to point out in the comments section. If this article has helped you, please like it and follow it.
In addition, I recommend to pay attention to my wechat public number [front-end technology river’s lake], in addition to the depth of good article, and I have carefully arranged [500 front end questions] waiting for you to check.