赞
踩
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事件系统当中的过程
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。