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的逻辑
// 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 在这里暂且不表,正常情况下,这一次调用是无效的,因为还没有真正的注入插件,所以谈不上对插件重新排序一说,进入就会立刻返回
// 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.',
);
}
}
}
主要起到导出工具函数的作用,这里简单介绍下这三个函数的左右
通过DOM节点拿到对应fiber节点上的props
通过fiber节点获取到对应的DOM节点,对于组件的fiber会返回null
通过DOM节点获取到对应的fiber节点
在进入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;
对应着ChangeEventPlugin.eventTypes.change
对应着ChangeEventPlugin本身
对应着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;
}
就是分别对全局变量registrationNameModules和registrationNameDependencies进行赋值
纵观整个流程,其实就是设置EventPluginRegistry.js文件中的各个全局变量,这里我们整理下被修改的全局变量,看下他们的key value都是什么
一个数组
eventPluginOrder = [
'ResponderEventPlugin',
'SimpleEventPlugin',
'EnterLeaveEventPlugin',
'ChangeEventPlugin',
'SelectEventPlugin',
'BeforeInputEventPlugin',
];
一个对象, key是插件名,value是插件
namesToPlugins = {
ChangeEventPlugin: ChangeEventPlugin,
// other plugin
}
一个存储着插件对象的数组,通过插件名在eventPluginOrder中的顺序存储着相应的插件
plugins = {
0: undefined,
// ... other plugin
3: ChangeEventPlugin,
// ... other plugin
}
一个对象,key是真实DOM对应的事件名
eventNameDispatchConfigs = {
change: ChangeEventPlugin.eventTypes.change,
// ... others
}
一个对象,key是react对应的事件名,value是对应的插件
registrationNameModules = {
onChange: ChangeEventPlugin,
onChangeCapture: ChangeEventPlugin,
}
一个对象,key是react对应的事件名,value是相关依赖的数组
registrationNameDependencies = {
onChange: ChangeEventPlugin.eventTypes.change.dependencies,
onChangeCapture: ChangeEventPlugin.eventTypes.change.dependencies,
}