Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React事件(三)注册,分发,处理和回收 #38

Open
xiaoxiaosaohuo opened this issue Nov 22, 2018 · 0 comments
Open

React事件(三)注册,分发,处理和回收 #38

xiaoxiaosaohuo opened this issue Nov 22, 2018 · 0 comments

Comments

@xiaoxiaosaohuo
Copy link
Owner

事件注册

直接从finalizeInitialChildren方法看起

function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  return shouldAutoFocusHostComponent(type, props);
}

改方法转而调用setInitialProperties ,从其名字可以看出是设置初始属性的。遍历props对象,给DOM设置一系列的属性,如style、class、autoFocus,innerHTML等,其中有一个case是处理事件的

else if (registrationNameModules.hasOwnProperty(propKey)) {
  if (nextProp != null) {
    if (true && typeof nextProp !== 'function') {
      warnForInvalidEventListener(propKey, nextProp);
    }
    ensureListeningTo(rootContainerElement, propKey);
  }

其中的 registrationNameModules这个变量 在React事件(一)中已经介绍过了,是注册的事件名对应的事件插件,注册的事件名是React事件名,以on开头,如 ‘onClick’。

加入添加了onClick事件,那么propKey ===onClick

就会执行ensureListeningTo方法

function ensureListeningTo(rootContainerElement, registrationName) {
  var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
  var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
  listenTo(registrationName, doc);
}

这个方法首先判断rootContainerElement是不是一个 document或者 Fragment,二者都不是的话,就设置doc为rootContainerElement.ownerDocument, 也就是当前节点的顶层的 document 对象。

可以看到事件委托就是在这里实现的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document。

接着看listenTo方法

var dependencies = registrationNameDependencies[registrationName];

for (var i = 0; i < dependencies.length; i++) {
    var dependency = dependencies[i];
    if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
      switch (dependency) {
        case TOP_SCROLL:
          trapCapturedEvent(TOP_SCROLL, mountAt);
          break;
        case TOP_FOCUS:
        case TOP_BLUR:
          trapCapturedEvent(TOP_FOCUS, mountAt);
          trapCapturedEvent(TOP_BLUR, mountAt);
          // We set the flag for a single dependency later in this function,
          // but this ensures we mark both as attached rather than just one.
          isListening[TOP_BLUR] = true;
          isListening[TOP_FOCUS] = true;
          break;
        case TOP_CANCEL:
        case TOP_CLOSE:
          if (isEventSupported(getRawEventName(dependency))) {
            trapCapturedEvent(dependency, mountAt);
          }
          break;
        case TOP_INVALID:
        case TOP_SUBMIT:
        case TOP_RESET:
          // We listen to them on the target DOM elements.
          // Some of them bubble so we don't want them to fire twice.
          break;
        default:
          // By default, listen on the top level to all non-media events.
          // Media events don't bubble so adding the listener wouldn't do anything.
          var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
          if (!isMediaEvent) {
            trapBubbledEvent(dependency, mountAt);
          }
          break;
      }
      isListening[dependency] = true;
    }
  }

dependencies 存储的是原生事件名称,可以看到,有两个方法,

  • trapCapturedEvent
  • trapBubbledEvent

像scroll,focus, blur,cancel,close,会调用trapCapturedEvent

一般事件默认走default分支,调用trapBubbledEvent方法

二者唯一的不同之处就在于,对于最终的合成事件,前者注册捕获阶段的事件监听器,而后者则注册冒泡阶段的事件监听器

由于事件委托到了document上,所有的合成事件回调函数都是在冒泡阶段触发的,所以无论是React的冒泡还是捕获事件,都是晚于原生冒泡和捕获事件的响应的,如果原生事件调用了e.stopPropagation(),那么React事件将无法响应

trapBubbledEvent

function trapBubbledEvent(topLevelType, element) {
  if (!element) {
    return null;
  }
  var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;

  addEventBubbleListener(element, getRawEventName(topLevelType),
  // Check if interactive and wrap in interactiveUpdates
  dispatch.bind(null, topLevelType));
}

dispatch就是dispatchEvent或者dispatchInteractiveEvent,dispatchInteractiveEvent最终也会执行dispatchEvent。

function addEventBubbleListener(element, eventType, listener) {
  element.addEventListener(eventType, listener, false);
}

参数

  • element 多数时候是document元素
  • eventType 事件类型 如click
  • listener dispatch方法

该方法用 addEventListener给 document注册了一个冒泡事件

事件分发

当事件触发的时候会调用刚刚注册的listener

function dispatchInteractiveEvent(topLevelType, nativeEvent) {
  interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}

接着调用dispatchEvent


function dispatchEvent(topLevelType, nativeEvent) {
  if (!_enabled) {
    return;
  }

  var nativeEventTarget = getEventTarget(nativeEvent);
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);
  if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
    
    targetInst = null;
  }

  var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);

  try {

    batchedUpdates(handleTopLevel, bookKeeping);
  } finally {
    releaseTopLevelCallbackBookKeeping(bookKeeping);
  }
}

通过nativeEvent找到对应的DOM实例和fiber node ;

其中在创建dom 的时候,会将对应的fiber node 设置到dom的internalInstanceKey属性上,即targetInst

 var nativeEventTarget = getEventTarget(nativeEvent);
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);

接着生成bookKeeping对象,

{
     topLevelType: topLevelType,
    nativeEvent: nativeEvent,
    targetInst: targetInst,
    ancestors: []
}

接下来进入批量更新阶段,其核心方法就是handleTopLevel

try {
    // Event queue being processed in the same cycle allows
    // `preventDefault`.
    batchedUpdates(handleTopLevel, bookKeeping);
  } finally {
  //释放bookKeeping
    releaseTopLevelCallbackBookKeeping(bookKeeping);
  }
  
  
  function handleTopLevel(bookKeeping) {
  var targetInst = bookKeeping.targetInst;

  // Loop through the hierarchy, in case there's any nested components.
  // It's important that we build the array of ancestors before calling any
  // event handlers, because event handlers can modify the DOM, leading to
  // inconsistencies with ReactMount's node cache. See #1105.
  var ancestor = targetInst;
  do {
    if (!ancestor) {
      bookKeeping.ancestors.push(ancestor);
      break;
    }
    var root = findRootContainerNode(ancestor);
    if (!root) {
      break;
    }
    bookKeeping.ancestors.push(ancestor);
    ancestor = getClosestInstanceFromNode(root);
  } while (ancestor);

  for (var i = 0; i < bookKeeping.ancestors.length; i++) {
    targetInst = bookKeeping.ancestors[i];
    runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
  }
}

在处理事件回调之前,根据当前的targetInst向上遍历,得到其所有的父级组件,存在ancestors中,我们知道事件委托到了document上,无论是冒泡还是捕获事件都有触发次序的问题,所以ancestors中,子元素排在了父元素前面。接下来就遍历ancestors,执行runExtractedEventsInBatch方法

runExtractedEventsInBatch

runExtractedEventsInBatch这个方法中调用了两个方法

  • extractEvents
  • runEventsInBatch

extractEvents用于构造合成事件,runEventsInBatch用于批处理 extractEvents构造出的合成事件

构造合成事件

function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var events = null;
  for (var i = 0; i < plugins.length; i++) {
    // Not every plugin in the ordering may be loaded at runtime.
    var possiblePlugin = plugins[i];
    if (possiblePlugin) {
      var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
      if (extractedEvents) {
        events = accumulateInto(events, extractedEvents);
      }
    }
  }
  return events;
}

这个plugins是个全局变量,在第一篇文章中有介绍,按插件注入顺序存储了各个事件插件。

遍历plugins,调用每个插件的extractEvents方法,找到对应的事件构造器,

case  case TOP_CLICK::
        EventConstructor = SyntheticMouseEvent;
        break;
        
        ....
        
   //省略一段代码
   
   
 const event = EventConstructor.getPooled(
  dispatchConfig,
  targetInst,
  nativeEvent,
  nativeEventTarget,
);
accumulateTwoPhaseDispatches(event);
return event;

getPooled是 EventConstructor上的一个方法,这个方法是在 EventConstructor初始化的时候挂上去的,其实就是 getPooledEvent。

getPooled就是从 event对象池中取出合成事件,将所有的事件缓存在对象池中,可以大大降低对象创建和销毁的时间,提升性能。

构造的react event 既有原生事件的接口属性,也有react自己的属性。

function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
  var EventConstructor = this;
  if (EventConstructor.eventPool.length) {
    var instance = EventConstructor.eventPool.pop();
    EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
    return instance;
  }
  return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
}

这个方法及其相关的方法在第二篇有介绍。请看React 事件(二)

构造完事件对象之后,调用 accumulateTwoPhaseDispatches方法,其中会调用traverseTwoPhase

traverseTwoPhase

向上遍历当前fiber node ,存储在path中。然后分别对捕获和冒泡阶段进行处理。

function traverseTwoPhase(inst, fn, arg) {
  var path = [];
  while (inst) {
    path.push(inst);
    inst = getParent(inst);
  }
  var i = void 0;
  for (i = path.length; i-- > 0;) {
    fn(path[i], 'captured', arg);
  }
  for (i = 0; i < path.length; i++) {
    fn(path[i], 'bubbled', arg);
  }
}

fn就是下面的accumulateDirectionalDispatches

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);
  }
}

listenerAtPhase 就是专门从当前的fibernode 上获取事件回调的。
取到回调之后,保存到事件event的 _dispatchListeners属性上,并且将当前元素及其父元素的FiberNode 保存到event的 _dispatchInstances属性上.

拿到所有与事件相关的元素实例以及事件的回调函数之后,就可以对合成事件进行批量处理了。

事件处理

runEventsInBatch 事件批处理的入口


function runEventsInBatch(events, simulated) {
  if (events !== null) {
    eventQueue = accumulateInto(eventQueue, events);
  }

  // Set `eventQueue` to null before processing it so that we can tell if more
  // events get enqueued while processing.
  var processingEventQueue = eventQueue;
  eventQueue = null;

  if (!processingEventQueue) {
    return;
  }

  if (simulated) {
    forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
  } else {
    forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
  }
  !!eventQueue ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : void 0;
  // This would be a good time to rethrow if any of the event handlers threw.
  rethrowCaughtError();
}

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

首先会将当前需要处理的 events事件,与之前没有处理完毕的队列调用 accumulateInto方法按照顺序进行合并,组合成一个新的队列,因为之前可能就存在还没处理完的合成事件,这里就可以执行了。

var executeDispatchesAndReleaseTopLevel = function (e) {
  return executeDispatchesAndRelease(e, false);
};


var executeDispatchesAndRelease = function (event, simulated) {
  if (event) {
    executeDispatchesInOrder(event, simulated);

    if (!event.isPersistent()) {
      event.constructor.release(event);
    }
  }
};

function executeDispatchesInOrder(event, simulated) {
  var dispatchListeners = event._dispatchListeners;
  var dispatchInstances = event._dispatchInstances;
  {
    validateEventDispatches(event);
  }
  if (Array.isArray(dispatchListeners)) {
    for (var i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) {
        break;
      }
      // Listeners and Instances are two parallel arrays that are always in sync.
      executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
    }
  } else if (dispatchListeners) {
    executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
  }
  event._dispatchListeners = null;
  event._dispatchInstances = null;
}

遍历 dispatchListeners ,如果event.isPropagationStopped()为 true,则遍历的循环直接 break掉,中断合成事件的向上遍历执行,也就起到了和原生事件调用 stopPropagation相同的效果。如果循环没有被中断,则继续执行 executeDispatch方法。最终会调用

func.apply(context, funcArgs);

func 就是事件回调 listener;

funcArgs 就是合成的事件对象event;

事件回收

事件执行完毕之后就会做一些清理的工作了。回到executeDispatchesAndRelease方法中,

if (!event.isPersistent()) {
      event.constructor.release(event);
    }

如果不持有事件对象的话就释放事件,调用

function releasePooledEvent(event) {
  var EventConstructor = this;
  !(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance into a pool of a different type.') : void 0;
  event.destructor();
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event);
  }
}

首先释放掉 event上属性占用的内存,然后把清理后的 event对象再放入对象池中,可以被后续事件对象二次利用。

至此 React 事件的一些基本概念和事件机制介绍完了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant