event 事件系统初始化
1 )概述
- react事件系统比较的复杂,它是基于dom的事件系统
- 在dom事件系统上面进行了一个深度的封装
- 它里面的很多实现逻辑都是自由的一套
- 在初始化 react-dom 的源码的时候,会为react的事件系统注入 reactdom 相关的一些插件
- 因为react事件系统,它有一个独立的模块,这个模块是一个公用性质的模块
- 就是说它是可以给 react-dom 用,也可以给 react-native 用
- 不同平台它们的事件系统可能会不一样,这个时候就对于不同的平台
- 它们要去使用同一个 event 模块的时候,使用注入的方式来注入一些跟平台相关的逻辑在里面
- 在这个模块,也是有一部分核心的内容是全平台通用的,这部分内容是react抽象出来的
- 我们关注平台插件注入的一个流程,以及它插入之后到底做了什么事情
- 首先要确定一个插件注入的顺序
- 因为在react当中它的插件执行是会按照顺序来的
- 如果不按顺序来,可能会出现一定的问题
- 然后要注入插件模块
- 最后要计算 registationNameModules 等属性
- 首先要确定一个插件注入的顺序
- 在之前 completeWork 的时候,初始化 dom 节点的时候
- 要去绑定 props 对应的 dom 的 attributes 的时候
- 就有遇到过这个 registationNameModules 属性
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;
- 它单纯的 export 出来了一个数组
- 这个数组可以看到有6项,每一项都以一个 Plugin 为结尾的
- 这些 plugin 都是在 react-dom 这个环境当中要用到的 plugin
- 这边只是用来定义这些 plugin 它的一个顺序
- 这个
后续
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, ], }, };
- eventTypes 这个对象里面还可以再加其他事件,以上是初始化时候挂载处理的 change 事件,参考下面
- 对于 packages/react-dom/src/events/SimpleEventPlugin.js 里面监听了大部分的常用事件
- 在这里面 会生成一个 type, 定位到
#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; }
- 在这里面 会生成一个 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事件系统当中的过程