“This is the second day of my participation in the First Challenge 2022.
When the React class component uses functions in JSX, it usually needs to bind the function to the current this. We know that this is not a React problem, but the default binding in Javascript. But in addition to how functions are called in JavaScript, I’d like to know more about how JSX’s bind events are performed in the Reac event system, and why React doesn’t help users automatically bind component instances.
Issue review
In JSX bind event -> to event trigger, because saving the call as a callback function causes the default bind to fall back and this to be lost
class TestComponent extends React.Component {
test() {
console.log(this) // undefined
}
render() {
return (
<button onClick={this.test}>test this</button>)}}Copy the code
The analysis reason
Implicit binding in JavaScript
Ordinary function calls
Normal function calls. In non-strict mode, the function’s this points to the window object by default, and in strict mode, undefined
function testThis() {
console.log(this)
}
testThis() // window
Copy the code
"use strict";
function testThis() {
console.log(this)
}
testThis() // undefined
Copy the code
Class method call
Note that class declarations are executed in strict mode, including constructors, static methods, prototype methods, getters, and setters.
View details on MDN
class TestThis {
name = 'testName'
test() {
console.log(this.name)
}
}
let test = new TestThis()
test.test() // testName
Copy the code
So how did this get lost in JSX? The following code explains why this is missing
class TestThis {
name = 'testName'
test() {
console.log(this)}}let testInstance = new TestThis()
let testFn = testInstance.test
testFn() // undefined
Copy the code
How to solve
The solution | Recommend index | Recommend index |
---|---|---|
Render the bind in this | Render executes bind multiple times | 🌟 |
Arrow function in render | Render generates new functions multiple times | 🌟 🌟 |
In the constructor to bind this | Constructor is not necessary sometimes | 🌟 🌟 🌟 |
Arrow function when defining a class method | recommended | 🌟 🌟 🌟 🌟 |
How does React call the event callback
The above explanation explains why this is missing in Javascript calls. Is this missing in JSX to bind events because of such calls?
From JSX to fiber
How does testThis bind from JSX to being executed
- First of all,
JSX
-> Turn to BabelReact.creatElement
class TestComponent extends Component {
testThis() {
console.log(this)}render() {
return (
<button onClick={this.testThis}>test this</button>)}}Copy the code
class Component {
testThis() {
console.log(this);
}
render() {
return React.createElement("button", {
onClick: this.testThis
})
}
}
Copy the code
- React.createElement -> Convert the react.element object
- React. element object -> Fiber object
From triggering to executing the event callback
React will be the first timecompleteWork
Stage according to thefiber
Create a real DOM and register all events that need to be registered in the DOMThe root element
The above.
Here’s how React bubbles testThis when a button is clicked.
First we click the button, and the listener is bound to the dispatchDiscreteEvent function. Listener is the dispatchDiscreteEvent function for discrete events. Click is a discrete event. But whatever the event is, dispatchEvent is eventually called, and dispatchEvent is used to distribute the event.
What does dispatchEvent do?
Call attemptToDispatchEvent
- Find the actual triggering dom element based on the nativeEvent object nativeEvent
export function attemptToDispatchEvent(domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, targetContainer: EventTarget, nativeEvent: AnyNativeEvent,) {
// find the actual triggering dom element based on the nativeEvent object nativeEvent
const nativeEventTarget = getEventTarget(nativeEvent);
// Get the corresponding fiber to trigger
// React generates a random pointer to the corresponding fiberNode when generating the real DOM, so fiber can be found through dom
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
// Ignore some code...
// Finally call the event handler plug-in event system
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
return null;
}
Copy the code
DispatchEventForPluginEventSystem invokes batchedUpdates batch execution dispatchEventsForPlugins, DispatchEventsForPlugins finally call extractEvents
What does extractevents mainly do
- Create composite events based on different types of events
- Collect listeners for each level of compliant events (
fiber
The body of thetestThis
) - The last generation
dispatchQueue
toprocessDispatchQueue
Calls to perform
ProcessDispatchQueue will cooperate with processDispatchQueueItemsInOrder simulate the bubbling or capture the listener, InvokeGuardedCallback eventually calls callCallback, but since invokeGuardedCallback doesn’t pass the context to callCallback, So func. Apply (context, funcArgs) is undefined
thinking
View the source code under review process, in fact in the plug-in event system (dispatchEventForPluginEventSystem) can take to the instance of a component, if preserved examples here, Is it possible to resolve the problem that the apply context is undefined when callCallback is executed?
conclusion
React execute event callback When onClick is triggered, the React listener on the root element will be invoked, and the listeners will be called on the plugins. The listeners will collect the functions callback on fiber based on different events and put it into the listeners array to form the dispatchQueue. Simulates bubbling to execute event callbacks in listeners.
Summary: The testThis event was saved as a callback when the listener was collected, causing this to be lost, and undefined is bound when the listener is executed by apply, resulting in undefined