NodeNote , continuous update react related library source code analysis , react TS3 project
Overview, background
<div id="app"></div>
<div onclick="alert(1)">原生</div>
class APP extends React.Component{
render(){
return (
<div>
<Header/>
</div>
)
}
}
class Header extends React.Component{
clickHandler(){
console.log("click")
}
render(){
return (
<div>
<div onClick={this.clickHandler.bind(this)} a={1}>
this is Header
</div>
<p onClick={this.clickHandler.bind(this)} a={1}>
this is Header
</p>
</div>
)
}
}
ReactDOM.render(
<APP/>,
document.getElementById('app')
);
复制代码
Trigger the process of the above-mentioned components click event as follows:
document
Listening to an
DOM
after clicking on the bubbling up of the event, call the
document
handler on
dispatchInteractiveEvent
:
function dispatchInteractiveEvent(topLevelType, nativeEvent) {
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
复制代码
interactiveUpdates
Action function is to perform:dispatchEvent(topLevelType, nativeEvent)
dispatchEvent(topLevelType, nativeEvent)
原因如下:
export function interactiveUpdates(fn, a, b) {
return _interactiveUpdatesImpl(fn, a, b);
}
let _interactiveUpdatesImpl = function(fn, a, b) {
return fn(a, b);
};
复制代码
dispatchEvent
Calls batchedUpdates
, which will be called handleTopLevel
, handleTopLevel
will callrunExtractedEventsInBatch
function runExtractedEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
// 合成事件的生成以及在fiber树上通过模拟捕获与冒泡收集事件处理函数与对应节点并存储到合成事件的相关属性上
var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
// 开始执行合成事件上的相关属性存储的执行事件处理函数
runEventsInBatch(events);
}
复制代码
runExtractedEventsInBatch
Function will first event name topLevelType
, the corresponding react
element instance targetInst
, the native target event nativeEvent
and the event action DOM
passed extractEvents
, the extractEvents
function will traverse the array plug event plugins
, and event name by passing topLevelType
selects a corresponding plugin
, and the call plugin
on extractEvents
, generate a synthesized events SyntheticEvent
, and collect the target node event-triggered and event handlers need more than an ancestor node for the trigger and fiber
are stored in the synthesis of the event _dispatchListeners
and _dispatchInstances
on the properties, functions and node capture process forward position in the array, ancestor node 'older 'the forward position and its event handler node in the array; bubbling phase function of the node in the array rearward position, ancestor nodes' positions on the older' and its event handler node in the array Rear;
Synthesis of the event object
runExtractedEventsInBatch函数中合成事件对象的逻辑:
var events = ex:tractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
复制代码
First look at generating synthetic event object
For click event calls is simpleeventplugin
the extractEvents
function, which will be passed parameters dispatchConfig, targetInst, nativeEvent, nativeEventTarget
, where dispatchConfig
by topLevelType
the topLevelEventsToDispatchConfig
configuration obtaining array, the simpleeventplugin.extractEvents
function calls the following code configuration of the event dispatchConfig
to the event corresponding to the react
element instance, native event, the native corresponding to the event DOM
encapsulated into a synthetic event.
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget)
复制代码
EventConstructor.getPooled
Is actually react\packages\events\SyntheticEvent.js
under the getPooledEvent
function, you can see from the code will fetch a synthetic event object from the event in the pool, and then use this object with a new dispatchConfig,targetInst,nativeEvent,nativeInst
re-initialization can be; if the event pool is empty, a newly created synthetic event object .
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this;
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
return instance;
}
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
}
复制代码
From the data structure to see the relationship between the event and the synthesis of native events
function SyntheticEvent(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget,
) {
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
const Interface = this.constructor.Interface;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
if (propName === 'target') {
this.target = nativeEventTarget;
} else {
this[propName] = nativeEvent[propName];
}
}
}
const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
复制代码
The synthesis can be seen in native event object events are stored nativeEvent
on the attribute, the target react
element instance stored in _targetInst
the attribute dispatchConfig
stored in dispatchConfig
the attribute, an event corresponding to the original raw DOM
i.e. nativeEventTarget
stored in the synthesis target property of the event. The native event object type
, eventPhase
, bubbles
, cancelable
, defaultPrevented
, isTrusted
property in the synthesis of the same name stored on the event.
Why would improve performance event pool
From the getPooledEvent
known data structure and function of the event object synthesis, React
synthesis SyntheticEvent
uses a pool of ideas, so as to achieve savings memory to avoid frequent create and destroy the purpose of the event object.
Event handler is then collected and stored with the node corresponding to the event object Synthesis
This is a particular event plug-in extractEvents
calling function EventConstructor.getPooled
after obtaining the synthetic event handler collection. That call is as followsaccumulateTwoPhaseDispatches(event)
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
accumulateTwoPhaseDispatches(event);
复制代码
accumulateTwoPhaseDispatches
Call stack collection event handler as follows:
export function traverseTwoPhase(inst, fn, arg) {
const path = [];
//将inst的父节点入栈,数组最后的为最远的祖先
while (inst) {
path.push(inst);
inst = getParent(inst);
}
let i;
//从最远的祖先开始向inst节点捕获执行fn
for (i = path.length; i-- > 0; ) {
fn(path[i], 'captured', arg);
}
//从inst节点开始向最远的祖先节点冒泡执行fn
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
}
复制代码
In the virtual DOM tree is actually a fiber
tree, the target node current events triggered by traversing up one by one into the ancestor node path
, and then start from the beginning is the corresponding traverse through the array from the tail to the head from an ancestor node to the destination node ( here simulation is to capture, so start from the ancestor node traversal down), this traversal, the traversal to the current node, synthetic event object that represents the capture or bubbling phase of the sign as a parameter accumulateDirectionalDispatches
, in which the implementation of listenerAtPhase
obtaining the binding event handlers to be triggered during the capture phase on that node, then get to the event handler listener
and the handler storage array on the incident event._dispatchListeners
passed accumulateInto
, and the current handler push
to event._dispatchListeners
the; same call accumulateInto
will capture the current stage binding the need to capture events triggered stages of storage nodes to event._dispatchInstances
, thus accumulateDirectionalDispatches
finished, that is, to collect all the stages of implementation need to capture the event handler corresponding to the node are stored in the synthesis of the event object _dispatchListeners
and _dispatchInstances
on. The capture phase, the parent node and its handler at the beginning part of the array, analog capture events triggered order.
According to the above logic, bubbling stage, in correspondence with traverseTwoPhase
the second of the for
cycles, will in turn call accumulateDirectionalDispatches
for the destination node and one or more event-triggered the parent node is associated with an event handler collection node event handler, and these functions and nodes are added to the synthetic event _dispatchListeners
and _dispatchInstances
on.
function accumulateDirectionalDispatches(inst, phase, event) {
{
!inst ? warningWithoutStack$1(false, 'Dispatching inst must not be null') : void 0;
}
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
// 获取捕获阶段的事件名registrationName
function listenerAtPhase(inst, event, propagationPhase) {
var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
// 开始根据registrationName在当前节点上的props中获取对应的事件处理函数
function getListener(inst, registrationName) {
var listener = void 0;
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
var stateNode = inst.stateNode;
if (!stateNode) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
var props = getFiberCurrentPropsFromNode(stateNode);
if (!props) {
// Work in progress.
return null;
}
listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
!(!listener || typeof listener === 'function') ? invariant(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener) : void 0;
return listener;
}
复制代码
So far, the following structure:
<div id='div1' onClick={this.clickHandler1.bind(this)} a={1}>
<div id='div2' onClick={this.clickHandler2.bind(this)} a={1}>
<div id='div3' onClick={this.clickHandler3.bind(this)} a={1}>
this is Header
</div>
</div>
</div>
复制代码
On the synthetic event _dispatchListeners
with _dispatchInstances
the are as follows:
_dispatchListener = [
clickHandler1,
clickHandler2,
clickHandler3
]
_dispatchInstances = [
id为'div1'的fiberNode,
id为'div2'的fiberNode,
id为'div3'的fiberNode
]
复制代码
Event handler execute on synthetic event object
runExtractedEventsInBatch 执行合成事件对象上的事件处理函数的逻辑:
runEventsInBatch(events);
复制代码
Function call stack triggers the process is as follows:
After the above logic, it calls on the synthesis of the event object from start to finish_dispatchListeners
the event on the handler. This paper focuses on the relationship between the synthesis clear event object and a native of the event object, and how to collect
fiber
event handler tree. As for how to do tomorrow is the content of the study. Of course, then this write it. From the
runEventsInBatch
start, the final event handler is invoked on the synthetic event:
//将新的合成事件对象添加到原来的对象队列中,然后进入下一个处理环节forEachAccumulated
export function runEventsInBatch(events) {
if (events !== null) {
// 将当前生成的合成事件对象或者合成事件对象数组添加到之前的合成事件对象队列中,构成新的队列
eventQueue = accumulateInto(eventQueue, events);
}
// 将新的合成事件对象队列eventQueue作为正在处理的队列processingEventQueue,并将前者清空
const processingEventQueue = eventQueue;
eventQueue = null;
if (!processingEventQueue) {return;}
//进入下一步
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
}
复制代码
runEventsInBatch
The new synthetic objects into the event queue to the original object , and then proceeds to the next processing stage forEachAccumulated
, showing react
the careful every function, every step of adding a corresponding error handling mechanism. forEachAccumulated
Function Source:
function forEachAccumulated(arr,cb,scope,) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope);
} else if (arr) {
cb.call(scope, arr);
}
}
复制代码
forEachAccumulated
Would be passed in arr
the array elements one by one to perform cb(arr[i])
, where cb
incoming to the previous step executeDispatchesAndReleaseTopLevel
, arr``为processingEventQueue
. arr
If the array is not executed directly cb(arr)
, and therefore mainly to see the logic executeDispatchesAndReleaseTopLevel:
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e);
};
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
复制代码
Look at the second step: look at here if
is if
, event.isPersistent
the return is always false
, so there will always perform release
reset event
property values and add to the vacant position of the event object pool:
react\packages\events\SyntheticEvent.js
//重置event上的属性值,并添加到事件对象池的空余位置
function releasePooledEvent(event) {
const EventConstructor = this;
//
event.destructor();
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(event);
}
}
//合成事件对象实例重置
destructor: function() {
const Interface = this.constructor.Interface;
for (const propName in Interface) {
this[propName] = null;
}
this.dispatchConfig = null;
this._targetInst = null;
this.nativeEvent = null;
this.isDefaultPrevented = functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
this._dispatchListeners = null;
this._dispatchInstances = null;
});
复制代码
Then look at a critical first step, executeDispatchesInOrder
how to perform the synthesis events on the event object handler:
export function executeDispatchesInOrder(event) {
const dispatchListeners = event._dispatchListeners;
const dispatchInstances = event._dispatchInstances;
if (Array.isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
复制代码
We can see from the above code cycle, walk the event._dispatchListeners
listener on first synthesized by the event object isPropagationStopped()
is blocking the further spread of the capture and bubbling phases in the current event to determine if there is a future event handler can not be carried out. If you do not stop the spread, then it calls executeDispatch
the event handler function, and ultimately on the synthesis of the event object _dispatchListeners
and _dispatchInstances
emptied. Now look executeDispatch
:
function executeDispatch(event, listener, inst) {
const type = event.type || 'unknown-event';
//获取当前fiber对应的真实DOM
event.currentTarget = getNodeFromInstance(inst);
//进入下一步
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
复制代码
executeDispatch
It will be passed in the current fiber
real DOM event object stored on the synthesis of currentTarget
the properties, and then synthesize the event object passed with fiber corresponding to the current event handler invokeGuardedCallbackAndCatchFirstError
:
react\packages\shared\ReactErrorUtils.js
function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f) {
invokeGuardedCallback.apply(this, arguments);
}
export function invokeGuardedCallback(name,func,context,a,b,c,d,e,f) {
invokeGuardedCallbackImpl.apply(reporter, arguments);
}
//在开发环境下invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
//invokeGuardedCallbackDev中涉及到如何处理错误,保证了系统的健壮性——TODO
//react\packages\shared\invokeGuardedCallbackImpl.js
let invokeGuardedCallbackImpl = function(name,func,context,a,b,c,d,e,f){
const funcArgs = Array.prototype.slice.call(arguments, 3);
try {
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
};
复制代码
By the final invokeGuardedCallbackImpl
call func.apply(context, funcArgs)
, the event handler function.
to sum up
- Synthesis of the event object initialization
- In
fiber
the trees analog capture and bubbling event, which is to simulate the capture phase, from ancient ancestor nodedocument
the destination node start to the event trigger traversal, traversing the process of collection event to be triggered during the capture phase handler and the corresponding node and stored in the synthesis related properties of the event object; then simulate bubbling phase, starting from the target node event trigger to ancient ancestor nodedocument
traversing, traversing the process of collection and storage prior to the event handler corresponding node to be triggered in the bubbling stage and deposit an array of property-related functions and node capture stage; at the beginning of two traversing more than once will traverse the event target node tree and thus into an array, then capture and bubbling all traverse the array. - Add a new synthesis of the event object to an existing event object queue, and then sequentially perform an array of functions event processing queue stored on each composite event object.
Eggs bind
Modification event listener function:
By addEventListener
event handler has only one input parameter for the event
event. as follows:
element.addEventListener(eventType, listener, true);
复制代码
Now demand is: when the event is triggered, want to call the following function
function dispatchInteractiveEvent(type, event) {
...
}
复制代码
The method is most likely to think:
const listener = function(event){
// 一些和type变量有关的逻辑
...
dispatchInteractiveEvent(type, event)
}
element.addEventListener(eventType, listener, true);
复制代码
Use bind achieve:
// 一些和type变量有关的逻辑
...
const listener = dispatchInteractiveEvent.bind(null, type);
element.addEventListener(eventType, listener, true);
复制代码
This difference between the two:
bind
You can implement type
logic into the outer scope, and before the trigger event type
to calculate better. The first method when an event triggers a call to the callback was calculated type
, this time will inevitably lead to a memory leak, because type
the logic of the outer layer may involve local variable scope. bind
Implementation, type
has been calculated Okay, so garbage collection automatically reclaims unused variables.
The total sentence is: first create the effect of closures.
Eggs apply clever splicing two arrays
Using apply
the array to the rear array b is added a
var a=[1,2],b=[3,4]
a.push.apply(a,b) // 返回数组a的长度
a // [1, 2, 3, 4]
b // [3, 4]
复制代码
apply
Here the main role is to be an array as a parameter, then one by one the implementation of elements in the array a.push(b[i])
, this must point to here a
.
Unlike the concat
var a=[1,2],b=[3,4]
a.concat(b) // 返回新的数组,由a与b数组组成
复制代码
to sum up
Here apply
you can save memory, do not need to create a new array.
Origin react source accumulateInto function
The holding function of the second parameter passed is not changed
function accumulateInto(
current,
next,
) {
if (current == null) {
return next;
}
if (Array.isArray(current)) {
if (Array.isArray(next)) {
current.push.apply(current, next);
return current;
}
current.push(next);
return current;
}
if (Array.isArray(next)) {
return [current].concat(next);
}
return [current, next];
}
复制代码
Reproduced in: https: //juejin.im/post/5d0afbd2e51d4510bf1d6690