Event react trigger event systems

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);
}
复制代码

interactiveUpdatesAction 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);
};
复制代码

dispatchEventCalls batchedUpdates, which will be called handleTopLevel, handleTopLevelwill callrunExtractedEventsInBatch

function runExtractedEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    // 合成事件的生成以及在fiber树上通过模拟捕获与冒泡收集事件处理函数与对应节点并存储到合成事件的相关属性上
    var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
    // 开始执行合成事件上的相关属性存储的执行事件处理函数
    runEventsInBatch(events);
}
复制代码

runExtractedEventsInBatchFunction will first event name topLevelType, the corresponding reactelement instance targetInst, the native target event nativeEventand the event action DOMpassed extractEvents, the extractEventsfunction will traverse the array plug event plugins, and event name by passing topLevelTypeselects a corresponding plugin, and the call pluginon 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 fiberare stored in the synthesis of the event _dispatchListenersand _dispatchInstanceson 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 simpleeventpluginthe extractEventsfunction, which will be passed parameters dispatchConfig, targetInst, nativeEvent, nativeEventTarget, where dispatchConfigby topLevelTypethe topLevelEventsToDispatchConfigconfiguration obtaining array, the simpleeventplugin.extractEventsfunction calls the following code configuration of the event dispatchConfigto the event corresponding to the reactelement instance, native event, the native corresponding to the event DOMencapsulated into a synthetic event.

var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget)
复制代码

EventConstructor.getPooledIs actually react\packages\events\SyntheticEvent.jsunder the getPooledEventfunction, 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,nativeInstre-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 nativeEventon the attribute, the target reactelement instance stored in _targetInstthe attribute dispatchConfigstored in dispatchConfigthe attribute, an event corresponding to the original raw DOMi.e. nativeEventTargetstored in the synthesis target property of the event. The native event object type, eventPhase, bubbles, cancelable, defaultPrevented, isTrustedproperty in the synthesis of the same name stored on the event.

Why would improve performance event pool

From the getPooledEventknown data structure and function of the event object synthesis, Reactsynthesis SyntheticEventuses 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 extractEventscalling function EventConstructor.getPooledafter obtaining the synthetic event handler collection. That call is as followsaccumulateTwoPhaseDispatches(event)

var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
accumulateTwoPhaseDispatches(event);
复制代码

accumulateTwoPhaseDispatchesCall stack collection event handler as follows:

traverseTwoPhase code in the previous article, there is analysis,

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 fibertree, 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 listenerAtPhaseobtaining the binding event handlers to be triggered during the capture phase on that node, then get to the event handler listenerand the handler storage array on the incident event._dispatchListenerspassed accumulateInto, and the current handler pushto event._dispatchListenersthe; same call accumulateIntowill capture the current stage binding the need to capture events triggered stages of storage nodes to event._dispatchInstances, thus accumulateDirectionalDispatchesfinished, 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 _dispatchListenersand _dispatchInstanceson. 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 traverseTwoPhasethe second of the forcycles, will in turn call accumulateDirectionalDispatchesfor 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 _dispatchListenersand _dispatchInstanceson.

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 _dispatchListenerswith _dispatchInstancesthe 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);
}
复制代码

runEventsInBatchThe new synthetic objects into the event queue to the original object , and then proceeds to the next processing stage forEachAccumulated, showing reactthe careful every function, every step of adding a corresponding error handling mechanism. forEachAccumulatedFunction Source:

function forEachAccumulated(arr,cb,scope,) {
  if (Array.isArray(arr)) {
    arr.forEach(cb, scope);
  } else if (arr) {
    cb.call(scope, arr);
  }
}
复制代码

forEachAccumulatedWould be passed in arrthe array elements one by one to perform cb(arr[i]), where cbincoming to the previous step executeDispatchesAndReleaseTopLevel, arr``为processingEventQueue. arrIf 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 ifis if, event.isPersistentthe return is always false, so there will always perform releasereset eventproperty 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, executeDispatchesInOrderhow 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._dispatchListenerslistener 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 executeDispatchthe event handler function, and ultimately on the synthesis of the event object _dispatchListenersand _dispatchInstancesemptied. 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;
}
复制代码

executeDispatchIt will be passed in the current fiberreal DOM event object stored on the synthesis of currentTargetthe 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 invokeGuardedCallbackImplcall func.apply(context, funcArgs), the event handler function.

to sum up

  1. Synthesis of the event object initialization
  2. In fiberthe trees analog capture and bubbling event, which is to simulate the capture phase, from ancient ancestor node documentthe 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 node documenttraversing, 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.
  3. 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 addEventListenerevent handler has only one input parameter for the eventevent. 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:

bindYou can implement typelogic into the outer scope, and before the trigger event typeto 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 typethe logic of the outer layer may involve local variable scope. bindImplementation, typehas 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 applythe 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]
复制代码

applyHere 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 applyyou 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

Guess you like

Origin blog.csdn.net/weixin_34303897/article/details/93172662