工程建设工程信息网,seo技术员,北京企业网站建设方,什么是电商行业event 事件系统初始化 1 #xff09;概述
react事件系统比较的复杂#xff0c;它是基于dom的事件系统在dom事件系统上面进行了一个深度的封装它里面的很多实现逻辑都是自由的一套在初始化 react-dom 的源码的时候#xff0c;会为react的事件系统注入 reactdom 相关的一些插…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个方法 injectEventPluginOrdersetComponentTree 这个先跳过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;// 遍历对象上的 pluginNamefor (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,);// 重新注入 modulenamesToPlugins[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 的顺序插入的而非有序插入这里可能会造成数组中的某几项为 undefinedplugins[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,);}}
}关于 eventTypesconst eventTypes {// 这个 对应 dom 中的真实事件比如 change 事件 document.addEventListener(change, () {})// 这个 change 代表 event name 存在// 这个 value 对应上面的 dispatchConfigchange: {// 事件的阶段有冒泡和捕获 两个阶段对应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;
}进入 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: PluginModuleAnyNativeEvent,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;// 获取事件 内部的 phasedRegistrationNamesconst 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: PluginModuleAnyNativeEvent,eventName: string,
): void {invariant(!registrationNameModules[registrationName],EventPluginHub: More than one plugin attempted to publish the same registration name, %s.,registrationName,);// onChange: ChangeEventPluginregistrationNameModules[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 inputsif (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: onClickcaptured: 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事件系统当中的过程