1 )概述
2 )源码
定位到 packages/react-dom/src/client/ReactDOM.js#L20
import './ReactDOMClientInjection';
再次定位到 packages/react-dom/src/client/ReactDOMClientInjection.js
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as EventPluginHub from 'events/EventPluginHub';
import * as EventPluginUtils from 'events/EventPluginUtils';
import {
getFiberCurrentPropsFromNode,
getInstanceFromNode,
getNodeFromInstance,
} from './ReactDOMComponentTree';
import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin';
import ChangeEventPlugin from '../events/ChangeEventPlugin';
import DOMEventPluginOrder from '../events/DOMEventPluginOrder';
import EnterLeaveEventPlugin from '../events/EnterLeaveEventPlugin';
import SelectEventPlugin from '../events/SelectEventPlugin';
import SimpleEventPlugin from '../events/SimpleEventPlugin';
/**
* Inject modules for resolving DOM hierarchy and plugin ordering.
*/
EventPluginHub.injection.injectEventPluginOrder(DOMEventPluginOrder);
EventPluginUtils.setComponentTree(
getFiberCurrentPropsFromNode,
getInstanceFromNode,
getNodeFromInstance,
);
/**
* Some important event plugins included by default (without having to require
* them).
*/
EventPluginHub.injection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,
});
看到它 import 了一大堆的东西,后续只是调用了3个方法
injectEventPluginOrder
setComponentTree
这个先跳过injectEventPluginsByName
看下 EventPluginHub.injection.injectEventPluginOrder(DOMEventPluginOrder);
DOMEventPluginOrder
// packages/react-dom/src/events/DOMEventPluginOrder.js
const DOMEventPluginOrder = [
'ResponderEventPlugin',
'SimpleEventPlugin',
'EnterLeaveEventPlugin',
'ChangeEventPlugin',
'SelectEventPlugin',
'BeforeInputEventPlugin',
];
export default DOMEventPluginOrder;
后续 EventPluginHub.injection.injectEventPluginsByName
这个方法的参数
ResponderEventPlugin
先不管关注下 EventPluginHub
这个模块下的 injection
/**
* Methods for injecting dependencies.
*/
export const injection = {
/**
* @param {array} InjectedEventPluginOrder
* @public
*/
injectEventPluginOrder,
/**
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
*/
injectEventPluginsByName,
};
./EventPluginRegistry.js
进入/**
* Injects an ordering of plugins (by plugin name). This allows the ordering
* to be decoupled from injection of the actual plugins so that ordering is
* always deterministic regardless of packaging, on-the-fly injection, etc.
*
* @param {array} InjectedEventPluginOrder
* @internal
* @see {EventPluginHub.injection.injectEventPluginOrder}
*/
export function injectEventPluginOrder(
injectedEventPluginOrder: EventPluginOrder,
): void {
invariant(
!eventPluginOrder,
'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
'once. You are likely trying to load more than one copy of React.',
);
// Clone the ordering so it cannot be dynamically mutated.
// 克隆一个可动态修改的数组
eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
recomputePluginOrdering();
}
/**
* Injects plugins to be used by `EventPluginHub`. The plugin names must be
* in the ordering injected by `injectEventPluginOrder`.
*
* Plugins can be injected as part of page initialization or on-the-fly.
*
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
* @internal
* @see {EventPluginHub.injection.injectEventPluginsByName}
*/
export function injectEventPluginsByName(
injectedNamesToPlugins: NamesToPlugins,
): void {
let isOrderingDirty = false;
// 遍历对象上的 pluginName
for (const pluginName in injectedNamesToPlugins) {
// 非本身拥有,则跳过
if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
continue;
}
const pluginModule = injectedNamesToPlugins[pluginName];
if (
!namesToPlugins.hasOwnProperty(pluginName) ||
namesToPlugins[pluginName] !== pluginModule
) {
invariant(
!namesToPlugins[pluginName],
'EventPluginRegistry: Cannot inject two different event plugins ' +
'using the same name, `%s`.',
pluginName,
);
// 重新注入 module
namesToPlugins[pluginName] = pluginModule;
isOrderingDirty = true; // 设置这个 isOrderingDirty 状态
}
}
if (isOrderingDirty) {
recomputePluginOrdering(); // 调用这个方法
}
}
namesToPlugins
本来就是一个 空的对象recomputePluginOrdering
/**
* Recomputes the plugin list using the injected plugins and plugin ordering.
*
* @private
*/
function recomputePluginOrdering(): void {
if (!eventPluginOrder) {
// Wait until an `eventPluginOrder` is injected.
return;
}
// 遍历在 injectEventPluginsByName 方法中处理好的 namesToPlugins 对象
for (const pluginName in namesToPlugins) {
const pluginModule = namesToPlugins[pluginName];
const pluginIndex = eventPluginOrder.indexOf(pluginName); // 拿到注入顺序
invariant(
pluginIndex > -1,
'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
'the plugin ordering, `%s`.',
pluginName,
);
// plugins 初始化的时候,是一个空的数组,存在则跳过
if (plugins[pluginIndex]) {
continue;
}
invariant(
pluginModule.extractEvents,
'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
'method, but `%s` does not.',
pluginName,
);
// 注意,这里的 index 是从 eventPluginOrder 的顺序插入的,而非有序插入,这里可能会造成数组中的某几项为 undefined
plugins[pluginIndex] = pluginModule;
const publishedEvents = pluginModule.eventTypes; // click, change, focus 等类型
for (const eventName in publishedEvents) {
invariant(
// 注意这里
publishEventForPlugin(
publishedEvents[eventName], // 注意这个数据结构
pluginModule,
eventName,
),
'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
eventName,
pluginName,
);
}
}
}
eventTypes
const eventTypes = {
// 这个 对应 dom 中的真实事件,比如 change 事件 document.addEventListener('change', () => {})
// 这个 change 代表 event name 存在
// 这个 value 对应上面的 dispatchConfig
change: {
// 事件的阶段,有冒泡和捕获 两个阶段,对应react中 使用的事件 props 名称
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture', // 这个 props 不常用,用于在绑定捕获阶段的事件监听
},
// 监听 change 事件的同时,需要依赖绑定下面的事件
dependencies: [
TOP_BLUR,
TOP_CHANGE,
TOP_CLICK,
TOP_FOCUS,
TOP_INPUT,
TOP_KEY_DOWN,
TOP_KEY_UP,
TOP_SELECTION_CHANGE,
],
},
};
#L143
(143行)function addEventTypeNameToConfig(
[topEvent, event]: EventTuple,
isInteractive: boolean,
) {
const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
const onEvent = 'on' + capitalizedEvent;
// 注意这里
const type = {
phasedRegistrationNames: {
bubbled: onEvent,
captured: onEvent + 'Capture',
},
dependencies: [topEvent],
isInteractive,
};
eventTypes[event] = type;
topLevelEventsToDispatchConfig[topEvent] = type;
}
publishEventForPlugin
/**
* Publishes an event so that it can be dispatched by the supplied plugin.
*
* @param {object} dispatchConfig Dispatch configuration for the event.
* @param {object} PluginModule Plugin publishing the event.
* @return {boolean} True if the event was successfully published.
* @private
*/
function publishEventForPlugin(
dispatchConfig: DispatchConfig,
pluginModule: PluginModule<AnyNativeEvent>,
eventName: string,
): boolean {
invariant(
!eventNameDispatchConfigs.hasOwnProperty(eventName),
'EventPluginHub: More than one plugin attempted to publish the same ' +
'event name, `%s`.',
eventName,
);
// 这里 eventNameDispatchConfigs 的结构
// { change: ChangeEventPlugin.eventTypes.change }
eventNameDispatchConfigs[eventName] = dispatchConfig;
// 获取事件 内部的 phasedRegistrationNames
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;
}
publishRegistrationName
/**
* Publishes a registration name that is used to identify dispatched events.
*
* @param {string} registrationName Registration name to add.
* @param {object} PluginModule Plugin publishing the event.
* @private
*/
function publishRegistrationName(
registrationName: string,
pluginModule: PluginModule<AnyNativeEvent>,
eventName: string,
): void {
invariant(
!registrationNameModules[registrationName],
'EventPluginHub: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName,
);
// onChange: ChangeEventPlugin
registrationNameModules[registrationName] = pluginModule;
// onChange: [TOP_BLUR ...]
registrationNameDependencies[registrationName] =
pluginModule.eventTypes[eventName].dependencies;
if (__DEV__) {
const lowerCasedName = registrationName.toLowerCase();
possibleRegistrationNames[lowerCasedName] = registrationName;
if (registrationName === 'onDoubleClick') {
possibleRegistrationNames.ondblclick = registrationName;
}
}
}
const publishedEvents = pluginModule.eventTypes;
这里,可参考 packages/react-dom/src/events/ChangeEventPlugin.js#L258const ChangeEventPlugin = {
eventTypes: eventTypes,
_isInputEventSupported: isInputEventSupported, // 这个 _isInputEventSupported 是一个私有标志位
// 这个 extractEvents 是生成事件,比如 onChange 事件对应的事件对象的
extractEvents: function(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
let getTargetInstFunc, handleEventFunc;
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(targetNode)) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
}
if (getTargetInstFunc) {
const inst = getTargetInstFunc(topLevelType, targetInst);
if (inst) {
const event = createAndAccumulateChangeEvent(
inst,
nativeEvent,
nativeEventTarget,
);
return event;
}
}
if (handleEventFunc) {
handleEventFunc(topLevelType, targetNode, targetInst);
}
// When blurring, set the value attribute for number inputs
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(targetNode);
}
},
};
通过以上操作,插入了所有的plugin之后,形成了这边的几个变量
let eventPluginOrder: EventPluginOrder = null;
数据结构如下[
'ResponderEventPlugin',
'SimpleEventPlugin',
'EnterLeaveEventPlugin',
'ChangeEventPlugin',
'SelectEventPlugin',
'BeforeInputEventPlugin'
];
export const plugins = [];
数据结构如下[
{
eventTypes:{},
extractEvents:function,
otherProps
},
....
]
export const eventNameDispatchConfigs = {};
{
click:{
dependencies:['click'],
phasedRegistrationNames:{
bubbled: "onClick"
captured: "onClickCapture"
},
isInteractive: true
}
}
const namesToPlugins: NamesToPlugins = {};
{
SimpleEventPlugin:{
eventTypes:{},
extractEvents:function,
otherProps
},
// ...其他插件
}
export const registrationNameModules = {};
{
onClick:{
eventTypes:{},
extractEvents:function,
otherProps
},
...
}
export const registrationNameDependencies = {};
{
onClick: ["click"],
onChange: [
"blur", "change", "click", "focus", "input", "keydown",keyup", "selectionchange
],
....
}
把这几个变量维护好之后,后面可以很方便的进行一些事件绑定相关的操作
对于事件注入这个模块,是初始化事件的前置任务
重点关注最终拿到的几个完成注册之后的变量的数据格式
以上就是把整个事件的插件它注入到react事件系统当中的过程