React16源码: React中event事件系统初始化源码实现

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;
            }
            
        • 进入 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#L258
          const 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事件系统当中的过程

你可能感兴趣的:(React,React,Native,react.js,前端,前端框架)