Warcry mpi
  1. 1 Warcry mpi
  2. 2 Last Surprise Lyn
  3. 3 Life Will Change Lyn
  4. 4 Libertus Chen-U
  5. 5 Time Bomb Veela
  6. 6 かかってこいよ NakamuraEmi
  7. 7 Quiet Storm Lyn
  8. 8 Flower Of Life 发热巫女
  9. 9 The Night We Stood Lyn
  10. 10 One Last You Jen Bird
  11. 11 Hypocrite Nush
2018-09-08 20:55:41

React源码解析——事件系统

和Vue类似,React同样实现了一套管理事件的系统,不过两者的实现思想还是有着不小的区别。Vue为每个实例维护了一个事件管理对象,而React将所有事件都代理到document再进行分发。这篇文章就来详细分析React中管理事件的思路。

事件注册

在React中注册事件回调函数的方式大家应该都很熟悉,实际上就是通过props传递,那么首先对事件处理的方法也应该在处理props的逻辑里。即在组件挂载和更新时,会调用的_updateDOMProperties方法中。

ReactDOMComponent.Mixin = {
  _updateDOMProperties: function (lastProps, nextProps, transaction) {
    ...
    for (propKey in nextProps) {
      if (registrationNameModules.hasOwnProperty(propKey)) {
        enqueuePutListener(this, propKey, nextProp, transaction);
      }
    }
  }
}

该方法中对每个props进行判断是否为事件属性,如果是,则进入到enqueuePutListener注册事件。

function enqueuePutListener(inst, registrationName, listener, transaction) {
  if (transaction instanceof ReactServerRenderingTransaction) {
    return;
  }
  var containerInfo = inst._hostContainerInfo;
  var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
  var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
  listenTo(registrationName, doc);
  transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener
  });
}

该函数主要做了两件事:

  1. 通过listenTo函数将事件注册到原生DOM的document上。
  2. 通过事务队列的形式调用putListener函数将事件存储起来。

document注册事件分发回调

先看listenTo源码

// ReactBrowserEventEmitter.js
listenTo: function (registrationName, contentDocumentHandle) {
  ...
  if (...) {
    ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(...);
  } else if (...) {
    ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(...);
  }
  ...
}

这段代码的主要目的是区分注册事件为捕获事件还是冒泡事件,并分别调用函数处理。再进入到两个处理函数:

// ReactEventListener.js
var ReactEventListener = {
  ...
  trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
    ...
    var handler = ReactEventListener.dispatchEvent.bind(null, topLevelType);
    return EventListener.listen(element, handlerBaseName, handler);
  },
  trapCapturedEvent: function (topLevelType, handlerBaseName, element) {
    var handler = ReactEventListener.dispatchEvent.bind(null, topLevelType);
    return EventListener.capture(element, handlerBaseName, handler);
  }
  dispatchEvent: function (topLevelType, nativeEvent) {
    ...
    ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    ...
  }
}

两个处理函数都是通过EventListener的listen方法为document注册回调,listen方法主要是兼容性处理,源码如下。可以看到无论是什么事件都是注册的相同回调方法dispatchEvent,说明document上注册的回调只起到事件分发的作用,不包含具体的事件处理。

  listen: function listen(target, eventType, callback) {
    if (target.addEventListener) {
      target.addEventListener(eventType, callback, false);
      return {
        remove: function remove() {
          target.removeEventListener(eventType, callback, false);
        }
      };
    } else if (target.attachEvent) {
      target.attachEvent('on' + eventType, callback);
      return {
        remove: function remove() {
          target.detachEvent('on' + eventType, callback);
        }
      };
    }
  }

那么既然事件的注册都是相同的分发回调,真正的回调函数又注册到哪了呢?这就是事务队列做的事了。

transaction存储事件处理回调

当事务处于close阶段时,将执行以上提到的putListener开启存储回调流程。

function putListener() {
  var listenerToPut = this;
  EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener);
}

该函数封装了一层代理,实际上调用了EventPluginHub上的putListener方法。

var listenerBank = {};
var getDictionaryKey = function (inst) {
  return '.' + inst._rootNodeID;
}
var EventPluginHub = {
  putListener: function (inst, registrationName, listener) {
    ...
    var key = getDictionaryKey(inst);
    var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
    bankForRegistrationName[key] = listener;
    ...
  }
}

事件存储管理对象为listerBank,按二维数组的结构对回调分类存储,两个key为React组件实例ID和事件类型。

到这一步就完成了React的事件注册了。

事件执行

由以上可知,所有事件的执行实际上都是调用了document上注册的dispatchEvent方法。而该方法也并没有什么关键逻辑,其任务是将事件分发的真正核心——handleTopLevelImpl方法放入批处理队列中。

function handleTopLevelImpl(bookKeeping) {
  var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);
  var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);

  var ancestor = targetInst;
  do {
    bookKeeping.ancestors.push(ancestor);
    ancestor = ancestor && findParent(ancestor);
  } while (ancestor);

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

该方法首先找到事件触发的DOM和React组件,然后由触发组件向上遍历获取所有父组件ancestors数组,并按冒泡顺序调用_handleTopLevel方法处理真正的回调。

_handleTopLevel: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var events = EventPluginHub.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
    runEventQueueInBatch(events);
  }

核心就是两件事:

  1. 调用extractEvents函数,根据原生事件构造出react合成事件
  2. 调用runEventQueueInBatch批处理队列中的合成事件

构造react合成事件

// EventPluginHub.js
var EventPluginHub = {
  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var events;
    var plugins = EventPluginRegistry.plugins;
    for (var i = 0; i < plugins.length; i++) {
      var possiblePlugin = plugins[i];
      if (possiblePlugin) {
        var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
        if (extractedEvents) {
          events = accumulateInto(events, extractedEvents);
        }
      }
    }
    return events;
  },
}

EventPluginHub的作用除了存储事件的回调函数之外,还存储着五种事件plugin用来合成事件。这些plugin在react初始化时就完成了注入。

// ReactDOM.js
var ReactDefaultInjection = require('./ReactDefaultInjection');
ReactDefaultInjection.inject();
...
// ReactDefaultInjection.js
module.exports = {
  inject: inject
};
function inject() {
  ...
  ReactInjection.EventPluginHub.injectEventPluginsByName({
    SimpleEventPlugin: SimpleEventPlugin,
    EnterLeaveEventPlugin: EnterLeaveEventPlugin,
    ChangeEventPlugin: ChangeEventPlugin,
    SelectEventPlugin: SelectEventPlugin,
    BeforeInputEventPlugin: BeforeInputEventPlugin
  });
  ...
}

以基础事件对应的Plugin:SimpleEventPlugin为例来分析合成事件过程:

var SimpleEventPlugin = {
  extractEvents: function (topLevelType, ...) {
    var EventConstructor;
    switch (topLevelType) {
      EventConstructor = one of [ SyntheticEvent, SyntheticKeyboardEvent, SyntheticFocusEvent, SyntheticMouseEvent, SyntheticDragEvent, SyntheticTouchEvent, SyntheticAnimationEvent, SyntheticTransitionEvent, SyntheticUIEvent, SyntheticWheelEvent, SyntheticClipboardEvent];
    }
    var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
    EventPropagators.accumulateTwoPhaseDispatches(event);
    return event;
  }
}

在Plugin进一步对事件类型进行细分,再根据细分类型从对象池中取出对象合成事件。

对象池是React事件的性能优化的一个关键,其关键思想在于当类的实例化开销较大并且可重用时,预先初始化好对象集合并维护,可以减少频繁创建和销毁对象的成本,实现缓存和复用。

下面为基本细分合成事件SyntheticEvent的源码:

function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
  ...
  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst;
  this.nativeEvent = nativeEvent;

  var Interface = this.constructor.Interface;
  for (var propName in Interface) {
    var normalize = Interface[propName];
    if (normalize) {
      this[propName] = normalize(nativeEvent);
    } else {
      if (propName === 'target') {
        this.target = nativeEventTarget;
      } else {
        this[propName] = nativeEvent[propName];
      }
    }
  }
  ...
}
_assign(SyntheticEvent.prototype, {
  preventDefault: function () { ... },
  stopPropagation: function () { ... },
  ...
});
var EventInterface = {
  type: null,
  target: null,
  currentTarget: emptyFunction.thatReturnsNull,
  eventPhase: null,
  bubbles: null,
  cancelable: null,
  timeStamp: function (event) {
    return event.timeStamp || Date.now();
  },
  defaultPrevented: null,
  isTrusted: null
};
SyntheticEvent.Interface = EventInterface;

SyntheticEvent.augmentClass = function (Class, Interface) {
  ...
}

获取合成事件后,再通过accumulateTwoPhaseDispatches方法从EventPluginHub中获取对应的回调函数。

// EventPropagators.js
function accumulateTwoPhaseDispatches(events) {
  forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateTwoPhaseDispatchesSingle(event) {
  if (event && event.dispatchConfig.phasedRegistrationNames) {
    EventPluginUtils.traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
  }
}
function accumulateDirectionalDispatches(inst, phase, event) {
  var listener = listenerAtPhase(inst, event, phase);
  if (listener) {
    event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
  }
}
var getListener = EventPluginHub.getListener;
function listenerAtPhase(inst, event, propagationPhase) {
  var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  return getListener(inst, registrationName);
}
// EventPluginHub.js
getListener: function (inst, registrationName) {
  var bankForRegistrationName = listenerBank[registrationName];
  var key = getDictionaryKey(inst);
  return bankForRegistrationName && bankForRegistrationName[key];
}

至此就完成了合成事件的生成,接下来就是批处理合成事件了。

批处理合成事件

批处理入口函数为runEventQueueInBatch

 function runEventQueueInBatch(events) {
    EventPluginHub.enqueueEvents(events);
    EventPluginHub.processEventQueue(false);
  }

  enqueueEvents: function (events) {
    if (events) {
      eventQueue = accumulateInto(eventQueue, events);
    }
  },

  processEventQueue: function (simulated) {
    var processingEventQueue = eventQueue;
    eventQueue = null;
    if (simulated) {
      forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
    } else {
      forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
    }
    ReactErrorUtils.rethrowCaughtError();
  },

先将待处理的新合成事件队列放入eventQueue中,再执行其中的事件,队列中也可能包含之前未处理的事件。

simulated标记用来判断是否为react测试代码,一般为false。

针对单个事件的处理入口函数为 executeDispatchesAndReleaseTopLevel。

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

var executeDispatchesAndRelease = function (event, simulated) {
  if (event) {
    EventPluginUtils.executeDispatchesInOrder(event, simulated);
    if (!event.isPersistent()) {
      event.constructor.release(event);
    }
  }
};

该函数先调用核心处理函数executeDispatchesInOrde处理完事件后,再将事件release掉减少内存开销。

function executeDispatchesInOrder(event, simulated) {
  var dispatchListeners = event._dispatchListeners;
  var dispatchInstances = event._dispatchInstances;

  if (Array.isArray(dispatchListeners)) {
    for (var i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) {
        break;
      }
      executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
    }
  } else if (dispatchListeners) {
    executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
  }
  event._dispatchListeners = null;
  event._dispatchInstances = null;
}

先取得JSX声明的回调和对应的React组件,再遍历实现类冒泡执行回调,当遇到阻止冒泡的属性时break掉循环,即阻止了冒泡。executeDispatch就是最终的回调执行函数了。

function executeDispatch(event, simulated, listener, inst) {
  var type = event.type || 'unknown-event';
  event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
  if (simulated) {
    ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
  } else {
    ReactErrorUtils.invokeGuardedCallback(type, listener, event);
  }
  event.currentTarget = null;
}

function invokeGuardedCallback(name, func, a) {
  try {
    func(a);
  } catch (x) {
    if (caughtError === null) {
      caughtError = x;
    }
  }
}

小结

相比Vue事件系统的观察者模式,React的事件系统更加突出了中介者模式。可以看到整套系统的核心流程在于以下几点:

  • Document元素作为中介,注册所有事件的分发处理函数。
  • EventPluginHub作为事件的管理hub,存储着所有真实回调并初始化合成事件处理对象池。
  • 事件触发时通过伪造冒泡收集所有事件触发组件和事件类型并在EventPluginHub合成事件对象
  • 批处理合成事件

-- EOF --

添加在分类「 前端开发 」下,并被添加 「React」 标签。