react 源码解读之react事件系统

181 阅读3分钟

react事件系统

本文讲解的react版本是16.13.1,在react17版本和react18版本中,会有差异,之后的文章会说到。

react事件系统大体可以分为以下三个部分:

  1. 初始化事件
  • 注入事件插件,设置一些对象,主要为后面的事件绑定和事件触发做准备。
  1. 事件绑定
  • 在react初始化DOM时,根据每个dom对应的fiber对象上是否有onClick这类事件props
  • 如果有,将事件代理到document上(在react17之后,代理到react应用的container上),包装一个统一事件处理函数dispatchEvent作为listener。
  1. 事件触发
  • 触发事件,事件冒泡到document,执行dispatchEvent。
  • 根据e.target,找到事件源dom,通过dom,获取对应fiber,从当前fiber向上遍历,找到所有绑定了相同事件的父级fiber,把react dom上绑定的事件函数放到事件队列中。
  • 模拟原生事件冒泡和捕获,执行全部事件。

接下来看源码是如何实现一套自己的事件系统。源码分析中,省略了一些不重要的代码,只展示了主要逻辑代码。

初始化事件系统

在初始化事件中,主要是注入事件插件,设置一些全局对象,为之后的事件绑定和事件触发做准备。例如在初始化过程中设置了registrationNameModules对象,记录所有事件对应要使用的事件插件。


var DOMEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];

injectEventPluginOrder(DOMEventPluginOrder);

injectEventPluginsByName({
 SimpleEventPlugin: SimpleEventPlugin,
 EnterLeaveEventPlugin: EnterLeaveEventPlugin,
 ChangeEventPlugin: ChangeEventPlugin,
 SelectEventPlugin: SelectEventPlugin,
 BeforeInputEventPlugin: BeforeInputEventPlugin
});
首先执行injectEventPluginOrder
  • 拷贝了传入的DOMEventPluginOrder,赋值给eventPluginOrder,后面会根据这个顺序生成plugins
  • 执行recomputePluginOrdering
  function injectEventPluginOrder(injectedEventPluginOrder) {

    // Clone the ordering so it cannot be dynamically mutated.
    eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
    recomputePluginOrdering();
  }

执行injectEventPluginsByName
function injectEventPluginsByName(injectedNamesToPlugins) {
  var isOrderingDirty = false;
  for (var pluginName in injectedNamesToPlugins) {
    var pluginModule = injectedNamesToPlugins[pluginName];
    if (!namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule) {
      namesToPlugins[pluginName] = pluginModule;
      isOrderingDirty = true;
    }
  }

  if (isOrderingDirty) {
    recomputePluginOrdering();
  }
}
  • 遍历injectedNamesToPlugins。把注入的所有插件,设置插件名称和插件模块的映射关系,记录到namesToPlugins
  • 最后执行recomputePluginOrdering
执行recomputePluginOrdering
function recomputePluginOrdering() {
    if (!eventPluginOrder) {
      return;
    }
    for (var pluginName in namesToPlugins) {
      var pluginModule = namesToPlugins[pluginName];
      var pluginIndex = eventPluginOrder.indexOf(pluginName);

      if (plugins[pluginIndex]) {
        continue;
      }

      plugins[pluginIndex] = pluginModule;
      var publishedEvents = pluginModule.eventTypes;

      for (var eventName in publishedEvents) {
        publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName)
      }
   }
}
  • 遍历namesToPlugins,每个循环中,看当前插件在 eventPluginOrder中的下标位置,取出对应的插件模块,把plugins对应的下标赋值为插件模块。
  • 获取当前遍历中的插件模块的eventTypes,遍历eventTypes执行publishEventForPlugin

在执行publishEventForPlugin之前,我们需要了解在一开始注入的各种事件插件的具体结构,以便知道上述的pluginModule.eventTypes是什么,还有接下来要做什么。下面以ChangeEventPlugin为例:

var eventTypes$1 = {
    change: {
      phasedRegistrationNames: {
        bubbled: 'onChange',
        captured: 'onChangeCapture'
      },
      dependencies: [TOP_BLUR, TOP_CHANGE, TOP_CLICK, TOP_FOCUS, TOP_INPUT, TOP_KEY_DOWN, TOP_KEY_UP, TOP_SELECTION_CHANGE]
    }
};

var ChangeEventPlugin = {
    eventTypes: eventTypes$1,
    _isInputEventSupported: isInputEventSupported,
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags) {
      ...
    }
};

  • 所以上一步中,获取当前遍历中的插件模块的eventTypes,遍历eventTypes执行publishEventForPlugin
  var publishedEvents = pluginModule.eventTypes;
  for (var eventName in publishedEvents) {
    publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName)
  }
调用publishEventForPlugin

第一个参数是:

{
  phasedRegistrationNames: {
    bubbled: 'onChange',
    captured: 'onChangeCapture'
  },
  dependencies: [TOP_BLUR, TOP_CHANGE, TOP_CLICK, TOP_FOCUS, TOP_INPUT, TOP_KEY_DOWN, TOP_KEY_UP, TOP_SELECTION_CHANGE]
}

第二个参数:事件插件模块
第三个参数:事件名称,以ChangeEventPlugin为例就是 change,其他插件中eventTypes可能不像ChangeEventPlugin只有一个change属性,会有多个,原理相同,有多少个就执行多少次

function publishEventForPlugin(dispatchConfig, pluginModule, eventName) {
  eventNameDispatchConfigs[eventName] = dispatchConfig;
  var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;

  if (phasedRegistrationNames) {
    for (var phaseName in phasedRegistrationNames) {
      if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
        var phasedRegistrationName = phasedRegistrationNames[phaseName];
        publishRegistrationName(phasedRegistrationName, pluginModule, eventName);
      }
    }
    return true;
  }
  return false;
}
  • 设置eventNameDispatchConfigs,将注入的所有插件的eventTypes属性里包含的事件名称用key记录下来,value对应dispatchConfig。生成下面格式:
{
  change: ChangePlugin.eventTypes.change,
  // ...other plugins
}
  • 取出dispatchConfigphasedRegistrationName属性{ bubbled: 'onChange', captured: 'onChangeCapture' }遍历,执行publishRegistrationName, 以ChangeEventPlugin插件为例:
    第一个参数分别传入onChange、onChangeCapture
    第二个参数传入ChangeEventPlugin
    第三个参数传入change
执行publishRegistrationName
function publishRegistrationName(registrationName, pluginModule, eventName) {
  // 1
  registrationNameModules[registrationName] = pluginModule;
  // 2
  registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies;
  
  {
    var lowerCasedName = registrationName.toLowerCase();
    possibleRegistrationNames[lowerCasedName] = registrationName;

    if (registrationName === 'onDoubleClick') {
      possibleRegistrationNames.ondblclick = registrationName;
    }
  }
}
  1. 设置registrationNameModules,记录事件和插件的映射关系,遍历完所有插件最后生成类似如下格式:
{
  ...
  onChange: ChangePlugin,
  onChangeCapture: ChangePlugin
  ...
}
  1. 设置registrationNameDependencies,记录事件和依赖事件的映射关系:
{
  onChange: ChangePlugin.eventTypes.change.dependencies,
  onChangeCapture: ChangePlugin.eventTypes.change.dependencies
}

事件绑定

未完待续...

事件触发

未完待续...