React 16 事件系统详解及源码解析(1)——注入平台插件

react为了跨平台的需求,事件系统采用的是插件注入的形式,这样只要针对不同的平台编写、注入相应的事件插件就能复用react的事件系统了

流程

react事件系统初始化的第一步就是注入平台相关的插件,打开react-dom package client平台下的ReactDOM.js,可以看到顶层的导入有这么一句

// ....some codes

import './ReactDOMClientInjection';

// ...some codes

就是这个ReactDOMClientInjection负责了事件系统相关的注入逻辑,我们看下它的代码内容

// ReactDOMClientInjection.js
import {
     setComponentTree} from 'legacy-events/EventPluginUtils';

import {
     
  getFiberCurrentPropsFromNode,
  getInstanceFromNode,
  getNodeFromInstance,
} from './ReactDOMComponentTree';

import LegacyBeforeInputEventPlugin from '../events/plugins/LegacyBeforeInputEventPlugin';
import LegacyChangeEventPlugin from '../events/plugins/LegacyChangeEventPlugin';
import LegacyEnterLeaveEventPlugin from '../events/plugins/LegacyEnterLeaveEventPlugin';
import LegacySelectEventPlugin from '../events/plugins/LegacySelectEventPlugin';
import LegacySimpleEventPlugin from '../events/plugins/LegacySimpleEventPlugin';

import ModernBeforeInputEventPlugin from '../events/plugins/ModernBeforeInputEventPlugin';
import ModernChangeEventPlugin from '../events/plugins/ModernChangeEventPlugin';
import ModernEnterLeaveEventPlugin from '../events/plugins/ModernEnterLeaveEventPlugin';
import ModernSelectEventPlugin from '../events/plugins/ModernSelectEventPlugin';
import ModernSimpleEventPlugin from '../events/plugins/ModernSimpleEventPlugin';

import {
     
  injectEventPluginOrder,
  injectEventPluginsByName,
  injectEventPlugins,
} from 'legacy-events/EventPluginRegistry';
import {
     enableModernEventSystem} from 'shared/ReactFeatureFlags';

if (enableModernEventSystem) {
     
  injectEventPlugins([
    ModernSimpleEventPlugin,
    ModernEnterLeaveEventPlugin,
    ModernChangeEventPlugin,
    ModernSelectEventPlugin,
    ModernBeforeInputEventPlugin,
  ]);
} else {
     
  const DOMEventPluginOrder = [
    'ResponderEventPlugin',
    'SimpleEventPlugin',
    'EnterLeaveEventPlugin',
    'ChangeEventPlugin',
    'SelectEventPlugin',
    'BeforeInputEventPlugin',
  ];

  injectEventPluginOrder(DOMEventPluginOrder);
  setComponentTree(
    getFiberCurrentPropsFromNode,
    getInstanceFromNode,
    getNodeFromInstance,
  );

  injectEventPluginsByName({
     
    SimpleEventPlugin: LegacySimpleEventPlugin,
    EnterLeaveEventPlugin: LegacyEnterLeaveEventPlugin,
    ChangeEventPlugin: LegacyChangeEventPlugin,
    SelectEventPlugin: LegacySelectEventPlugin,
    BeforeInputEventPlugin: LegacyBeforeInputEventPlugin,
  });
}

首先执行了injectEventPluginOrder(DOMEventPluginOrder),DOMEventPluginOrder就是个存储着插件名的数组,后边注入插件的时候,插件的顺序会和这个数组对应

injectEventPluginOrder

我们先看下injectEventPluginOrder的逻辑

// EventPluginRegistry.js
export function injectEventPluginOrder(
  injectedEventPluginOrder: EventPluginOrder,
): void {
     
  // Clone the ordering so it cannot be dynamically mutated.
  eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
  recomputePluginOrdering();
}

injectEventPluginOrder的逻辑很简单,就是更改了自身所在的EventPluginRegistry.js文件中全局变量eventPluginOrder的值,将传入的插件名数组DOMEventPluginOrder复制了一份,赋值给了eventPluginOrder,最后调用recomputePluginOrdering函数

recomputePluginOrdering 在这里暂且不表,正常情况下,这一次调用是无效的,因为还没有真正的注入插件,所以谈不上对插件重新排序一说,进入就会立刻返回

setComponentTree

// EventPluginUtils.js
export let getFiberCurrentPropsFromNode = null;
export let getInstanceFromNode = null;
export let getNodeFromInstance = null;

export function setComponentTree(
  getFiberCurrentPropsFromNodeImpl,
  getInstanceFromNodeImpl,
  getNodeFromInstanceImpl,
) {
     
  getFiberCurrentPropsFromNode = getFiberCurrentPropsFromNodeImpl;
  getInstanceFromNode = getInstanceFromNodeImpl;
  getNodeFromInstance = getNodeFromInstanceImpl;
  if (__DEV__) {
     
    if (!getNodeFromInstance || !getInstanceFromNode) {
     
      console.error(
        'EventPluginUtils.setComponentTree(...): Injected ' +
          'module is missing getNodeFromInstance or getInstanceFromNode.',
      );
    }
  }
}

主要起到导出工具函数的作用,这里简单介绍下这三个函数的左右

  • getFiberCurrentPropsFromNode

通过DOM节点拿到对应fiber节点上的props

  • getInstanceFromNode

通过fiber节点获取到对应的DOM节点,对于组件的fiber会返回null

  • getNodeFromInstance

通过DOM节点获取到对应的fiber节点

injectEventPluginsByName

在进入injectEventPluginsByName前,我们先看看一个plugin长什么样,这里我们用处理onChange的ChangeEventPlugin举例子

// EventPluginRegistry.js
const eventTypes = {
     
  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,
    ],
  },
};

const ChangeEventPlugin = {
     
  eventTypes: eventTypes,
	
  _isInputEventSupported: isInputEventSupported, // 一个标志位

  extractEvents: function(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
  ) {
     
    // ...
    // ...
    // some codes
};

export default ChangeEventPlugin;

ChangeEventPlugin.extractEvents是生成event对象的函数,当我们触发onChange事件时,拿到的event对象就是这个函数产生的,不同的事件有着自身生成event对象的函数。

ChangeEventPlugin.eventTypes,存储着实际的DOM事件名称,例如onChange对应的就是change事件。注意,eventTypes是一个对象,是因为一个插件可以处理多个事件,SimpleEventPlugin.eventTypes里就存储着大量的事件名称

eventTypes.change.phasedRegistrationNames对应着不同阶段会触发的react事件名,这里可以看出,如果你想在获取捕获阶段的event的对象,你该使用在onChangeCapture上绑定函数,而不是onChange

eventTypes.change.dependencies表明了这个事件需要依赖的其他事件,换句话说,当我们在react中绑定了onChange事件时,react不仅帮我们绑定change事件还有其依赖的事件,至于为什么。还记得原生DOM的input框的change事件,必须在blur时才会触发吗?而react中的onChange事件,每一次输入都会触发,这里是react帮助我们做到的,为此就需要其他事件的配合

接下来,我们可以进入到injectEventPluginsByName中看一下,这个函数到底做了什么了

// EventPluginRegistry.js
export function injectEventPluginsByName(
  injectedNamesToPlugins: NamesToPlugins,
): void {
     
  let isOrderingDirty = false;
  for (const pluginName in injectedNamesToPlugins) {
     
    if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
     
      continue;
    }
    const pluginModule = injectedNamesToPlugins[pluginName];
    if (
      !namesToPlugins.hasOwnProperty(pluginName) ||
      namesToPlugins[pluginName] !== pluginModule
    ) {
     
      namesToPlugins[pluginName] = pluginModule;
      isOrderingDirty = true;
    }
  }
  if (isOrderingDirty) {
     
    recomputePluginOrdering();
  }
}

代码逻辑很简单,就是把我们传入的插件,按照原来的key名赋值到全局变量namesToPlugins 上,namesToPlugins 这个变量在开始时是个空对象,这里才被真正赋值

第一次调用这个方法,isOrderingDirty肯定会被设置为true,这里我们可以深入看下recomputePluginOrdering到底做了什么

// EventPluginRegistry.js

function recomputePluginOrdering(): void {
     
  if (!eventPluginOrder) {
     
    // Wait until an `eventPluginOrder` is injected.
    return;
  }
  for (const pluginName in namesToPlugins) {
     
    const pluginModule = namesToPlugins[pluginName];
    const pluginIndex = eventPluginOrder.indexOf(pluginName);
    if (plugins[pluginIndex]) {
     
      continue;
    }
    
    plugins[pluginIndex] = pluginModule;
    const publishedEvents = pluginModule.eventTypes;
    for (const eventName in publishedEvents) {
     
      invariant(
        publishEventForPlugin(
          publishedEvents[eventName],
          pluginModule,
          eventName,
        ),
        'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
        eventName,
        pluginName,
      );
    }
  }
}

开头遍历namesToPlugins,通过key值(插件名)拿到在eventPluginOrder的顺序,赋值到全局变量plugins上。这里有个需要注意的小点,返回文章开头,在ReactDOM.js中,你可以看到eventPluginOrder中有6个插件名,但下面我们只注入了5个插件,也就是说在遍历完成后,plugins[0] 的值是 undefined

在遍历每一个插件的过程中,会对每一个插件.eventTypes里的每个对象调用publishEventForPlugin()方法,传了三个参数,我们看看分别是什么,这里我们依然用ChangeEventPlugin举例子,我们先回顾下ChangeEventPlugin长什么样子

// EventPluginRegistry.js
const eventTypes = {
     
  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,
    ],
  },
};

const ChangeEventPlugin = {
     
  eventTypes: eventTypes,
	
  _isInputEventSupported: isInputEventSupported, // 一个标志位

  extractEvents: function(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
  ) {
     
    // ...
    // ...
    // some codes
};

export default ChangeEventPlugin;
  • publishedEvents[eventName]

对应着ChangeEventPlugin.eventTypes.change

  • pluginModule

对应着ChangeEventPlugin本身

  • eventName

对应着ChangeEventPlugin.eventTypes的key名,这里是‘change’

接下来,我们进入publishEventForPlugin里面看看

// EventPluginRegistry.js

function publishEventForPlugin(
  dispatchConfig: DispatchConfig,
  pluginModule:
    | LegacyPluginModule<AnyNativeEvent>
    | ModernPluginModule<AnyNativeEvent>,
  eventName: string,
): boolean {
     
  eventNameDispatchConfigs[eventName] = dispatchConfig;

  const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
  if (phasedRegistrationNames) {
     
    for (const phaseName in phasedRegistrationNames) {
     
      if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
     
        const phasedRegistrationName = phasedRegistrationNames[phaseName];
        publishRegistrationName(
          phasedRegistrationName,
          pluginModule,
          eventName,
        );
      }
    }
    return true;
  } else if (dispatchConfig.registrationName) {
     
    publishRegistrationName(
      dispatchConfig.registrationName,
      pluginModule,
      eventName,
    );
    return true;
  }
  return false;
}

首先是对全局变量**eventNameDispatchConfigs[eventName]**设置相应的dispatchConfig,dispatchConfig就是刚才说的ChangeEventPlugin.eventTypes.change对象

后面两个判断,目的都是为了拿到事件对应的react事件名,因为有的事件不支持冒泡,所以就不用特别指明冒泡和捕获的事件名,这里用了registrationName和phasedRegistrationNames来辨别,前者表示不用区分不同阶段的事件名,我自身就是事件名,最后都是调用publishRegistrationName

我们看下 publishRegistrationName

// EventPluginRegistry.js

function publishRegistrationName(
  registrationName: string,
  pluginModule:
    | LegacyPluginModule<AnyNativeEvent>
    | ModernPluginModule<AnyNativeEvent>,
  eventName: string,
): void {
     
  registrationNameModules[registrationName] = pluginModule;
  registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies;

}

就是分别对全局变量registrationNameModulesregistrationNameDependencies进行赋值

总结

纵观整个流程,其实就是设置EventPluginRegistry.js文件中的各个全局变量,这里我们整理下被修改的全局变量,看下他们的key value都是什么

eventPluginOrder

一个数组

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

namesToPlugins

一个对象, key是插件名,value是插件

namesToPlugins = {
     
	ChangeEventPlugin: ChangeEventPlugin,
	// other plugin
}

plugins

一个存储着插件对象的数组,通过插件名在eventPluginOrder中的顺序存储着相应的插件

plugins = {
     
	0: undefined,
	// ... other plugin
	3: ChangeEventPlugin,
	// ... other plugin
}

eventNameDispatchConfigs

一个对象,key是真实DOM对应的事件名

eventNameDispatchConfigs = {
     
	change: ChangeEventPlugin.eventTypes.change,
	// ... others
}

registrationNameModules

一个对象,key是react对应的事件名,value是对应的插件

registrationNameModules = {
     
	onChange: ChangeEventPlugin,
	onChangeCapture: ChangeEventPlugin,
}

registrationNameDependencies

一个对象,key是react对应的事件名,value是相关依赖的数组

registrationNameDependencies = {
     
	onChange: ChangeEventPlugin.eventTypes.change.dependencies,
	onChangeCapture: ChangeEventPlugin.eventTypes.change.dependencies,
}

你可能感兴趣的:(react,javascript,reactjs)