当前位置:   article > 正文

React16源码: React中event事件对象的创建过程源码实现_nodes.filter(node => node.name.tolowercase().inclu

nodes.filter(node => node.name.tolowercase().includes(inputvalue));

event 对象


1 ) 概述

  • 在生产事件对象的过程当中,要去调用每一个 possiblePlugin.extractEvents 方法
  • 现在单独看下这里面的细节过程,即如何去生产这个事件对象的过程

2 )源码

定位到 packages/events/EventPluginHub.js#L172

function extractEvents(
  topLevelType: TopLevelType,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: EventTarget,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
  let events = null;
  for (let i = 0; i < plugins.length; i++) {
    // Not every plugin in the ordering may be loaded at runtime.
    const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
    if (possiblePlugin) {
      // 这里要去调用 每个 plugin 的 extractEvents 方法
      const extractedEvents = possiblePlugin.extractEvents(
        topLevelType,
        targetInst,
        nativeEvent,
        nativeEventTarget,
      );
      if (extractedEvents) {
        events = accumulateInto(events, extractedEvents);
      }
    }
  }
  return events;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

注意这里的 possiblePlugin.extractEvents 我们专门专注下 changeEvent 的这个方法
定位到 packages/react-dom/src/events/ChangeEventPlugin.js#L263

const ChangeEventPlugin = {
  eventTypes: eventTypes,

  _isInputEventSupported: isInputEventSupported,
  // 注意这里
  extractEvents: function(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget,
  ) {
    // 在这个方法里面,拿到了 targetNode
    // 因为这边传进来的呢是一个发布对象, 所以要通过这种方法拿到它的 node
    const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;

    // 然后,要经过一系列的判断,主要去赋值了不同的 getTargetInstFunc
    let getTargetInstFunc, handleEventFunc;
    // 如果有返回这个instance,那么我们就可以去创建这个event了,这是什么意思呢?
    // 就是说我们这个 event plugin, 在所有的事件触发的过程当中,这个plugin都会被循环调用的
    // 它是没有通过事件名称来调用不同的plugin这么一个设置的
    // 所以这个判断是要放在每个pluggin里面自己去做。就是说根据这次触发的具体事件是什么?
    // 来判断我们要不要为它创建一个event,因为每个plugin在每次事件触发都会被调用
    // 如果我们都生成事件,那么明显是不对的,肯定要对自己这个 plugin 关心的事件来去为它生成这个事件
    if (shouldUseChangeEvent(targetNode)) {
      getTargetInstFunc = getTargetInstForChangeEvent;
    } else if (isTextInputElement(targetNode)) {
      if (isInputEventSupported) {
        getTargetInstFunc = getTargetInstForInputOrChangeEvent;
      } else {
        // polyfill 的这些先忽略
        getTargetInstFunc = getTargetInstForInputEventPolyfill;
        handleEventFunc = handleEventsForInputEventPolyfill;
      }
    } else if (shouldUseClickEvent(targetNode)) {
      getTargetInstFunc = getTargetInstForClickEvent;
    }

    // 基于类型,得到了最终的处理函数
    if (getTargetInstFunc) {
      const inst = getTargetInstFunc(topLevelType, targetInst);
      if (inst) {
        // 创建 event
        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);
    }
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 进入 shouldUseChangeEvent

    function shouldUseChangeEvent(elem) {
      const nodeName = elem.nodeName && elem.nodeName.toLowerCase();
      return (
        nodeName === 'select' || (nodeName === 'input' && elem.type === 'file')
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 这个判断其实就是主要来判断一下我们这个节点上面是否有changeevent
    • 并且是否应该用 change event 来进行一个触发
    • 因为react当中的 onchange 事件,其实它是封装了各种不同的事件的
    • 比如说对于像我们输入文本的 input type=‘text’ 的一个情况
    • 正常来讲,应该绑定的是input事件,而不是change事件
    • 因为change事件在有些浏览器里面要等到这个输入框,blur的时候,才会真正触发这个change事件
    • 对于input事件是我们每次有输入变化的时候,都会触发的这个事件
    • 所以对于 select 还有 input type=‘file’, 它们的change是非常明显的,就是等到它们有内容变化的时候就会触发
    • 因为file是我们选择了一个文件之后,它就会触发change事件
    • 而select我们选择了某一个 option 之后,它也会触发这个change事件
    • 所以对于这种节点,我们可以直接使用onchange来进行一个绑定
    • 这时候, getTargetInstFunc = getTargetInstForChangeEvent; 进入 getTargetInstForChangeEvent
      function getTargetInstForChangeEvent(topLevelType, targetInst) {
        // TOP_CHANGE 就是 change
        if (topLevelType === TOP_CHANGE) {
          return targetInst;
        }
      }
      
      // 注意 另外的文件中
      // packages/react-dom/src/events/DOMTopLevelEventTypes.js#L41
      export const TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');
      
      // packages/events/TopLevelEventTypes.js#L27
      export function unsafeCastStringToDOMTopLevelType(
        topLevelType: string,
      ): DOMTopLevelEventType {
        return topLevelType;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
  • 进入 isTextInputElement

    const supportedInputTypes: {[key: string]: true | void} = {
      color: true,
      date: true,
      datetime: true,
      'datetime-local': true,
      email: true,
      month: true,
      number: true,
      password: true,
      range: true,
      search: true,
      tel: true,
      text: true,
      time: true,
      url: true,
      week: true,
    };
    
    function isTextInputElement(elem: ?HTMLElement): boolean {
      // 获取 nodeName
      const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
    
      // 判断在 input 的时候,是否符合支持的type类型
      if (nodeName === 'input') {
        return !!supportedInputTypes[((elem: any): HTMLInputElement).type];
      }
    
      if (nodeName === 'textarea') {
        return true;
      }
    
      return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 这里就是一个 boolean 类型的函数进行类型判断的
  • 进入 getTargetInstForInputOrChangeEvent

    // packages/react-dom/src/events/ChangeEventPlugin.js#L229
    function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
      if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
        return getInstIfValueChanged(targetInst);
      }
    }
    
    // packages/react-dom/src/events/ChangeEventPlugin.js#L106
    function getInstIfValueChanged(targetInst) {
      const targetNode = getNodeFromInstance(targetInst);
      if (inputValueTracking.updateValueIfChanged(targetNode)) {
        return targetInst;
      }
    }
    
    // packages/react-dom/src/client/ReactDOMComponentTree.js#L69
    export function getNodeFromInstance(inst) {
      if (inst.tag === HostComponent || inst.tag === HostText) {
        // In Fiber this, is just the state node right now. We assume it will be
        // a host component or host text.
        return inst.stateNode;
      }
    
      // Without this first invariant, passing a non-DOM-component triggers the next
      // invariant for a missing parent, which is super confusing.
      invariant(false, 'getNodeFromInstance: Invalid argument.');
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 进入 shouldUseClickEvent

    /**
     * SECTION: handle `click` event
     */
     // checkbox 和 radio 的特殊处理
    function shouldUseClickEvent(elem) {
      // Use the `click` event to detect changes to checkbox and radio inputs.
      // This approach works across all browsers, whereas `change` does not fire
      // until `blur` in IE8.
      const nodeName = elem.nodeName;
      return (
        nodeName &&
        nodeName.toLowerCase() === 'input' &&
        (elem.type === 'checkbox' || elem.type === 'radio')
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 进入 getTargetInstForClickEvent

    function getTargetInstForClickEvent(topLevelType, targetInst) {
      if (topLevelType === TOP_CLICK) {
        return getInstIfValueChanged(targetInst);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 进入 createAndAccumulateChangeEvent 创建事件对象

    function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
      const event = SyntheticEvent.getPooled(
        eventTypes.change,
        inst,
        nativeEvent,
        target,
      );
      event.type = 'change';
      // Flag this event loop as needing state restore.
      enqueueStateRestore(target);
      accumulateTwoPhaseDispatches(event);
      return event;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 这个函数就是具体生成这个事件的一个过程
      • 可以看到这个事件,接收了一个 inst,然后传入了 nativeEvent,并且再传入 target
      • 然后,通过 SyntheticEvent.getPooled,就是说在react当中所有的事件对象是通过一个 pool 来进行一个存储的
      • 比如说我们为所有的event创建了十个event对象
      • 每一次有新的一个event进来的时候,从这个pool里面拿出一个设置一些事件以及对应的一些值之后
      • 去触发每一个事件的监听方法,然后去使用这个 event 对象
      • 这个 event 对象使用完了之后,又会归还到这个 pool 里面
      • 也就是一个 能够减少 对象声明 以及 对象回收 的一个性能开销
      • 然后拿到了这个 event 之后,给它设置了 type 是 change
      • 之后执行两个函数 enqueueStateRestoreaccumulateTwoPhaseDispatches
    • 进入 SyntheticEvent.getPooled
      // packages/events/SyntheticEvent.js#L335
      function addEventPoolingTo(EventConstructor) {
        EventConstructor.eventPool = [];
        EventConstructor.getPooled = getPooledEvent; // 注意这里
        EventConstructor.release = releasePooledEvent;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 进入 getPooledEvent
        // packages/events/SyntheticEvent.js#L300
        function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
          const EventConstructor = this;
          // 存在poll
          if (EventConstructor.eventPool.length) {
            const instance = EventConstructor.eventPool.pop();
            EventConstructor.call(
              instance,
              dispatchConfig,
              targetInst,
              nativeEvent,
              nativeInst,
            );
            return instance;
          }
          // pool 里面没有,则创建一个新的
          return new EventConstructor(
            dispatchConfig,
            targetInst,
            nativeEvent,
            nativeInst,
          );
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 关于 EventConstructor
          • 首先在 packages/events/SyntheticEvent.js#L62 中的 SyntheticEvent 构造方法
          • 要理解这个过程, 首先在这个js里面先声明了一个叫做 SyntheticEvent 这么一个方法
          • 这个方法它是一个constructor 方法, 在这个方法里面去声明事件相关的各种属性
          • 重点关注它的事件的一个触发的过程以及生产的过程,所以只关心它的 pool 的处理过程
        • 这里的 this 就是 SyntheticEvent
          • 在这个构造方法的原型链上也有一大堆的东西,对事件对象进行一个封装和扩展
        • 注意这里的 EventConstructor.callnew EventConstructor 都达到同一个目的
    • 进入 enqueueStateRestore
      export function enqueueStateRestore(target: EventTarget): void {
        // 判断了这个 restoreTarget 公共变量是否存在
        if (restoreTarget) {
          if (restoreQueue) {
            restoreQueue.push(target);
          } else {
            restoreQueue = [target];
          }
        // restoreTarget 不存在,则对其进行赋值
        } else {
          restoreTarget = target;
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 作用是处理,如果setState之后,这个 state 对应的 input 的 value 是不一样的
      • 要把这个值进行一个回滚
    • 进入 accumulateTwoPhaseDispatches 这个方法才是真正要去从每个节点上面去获取它的 listener 的一个过程
      // packages/events/EventPropagators.js#L115
      export function accumulateTwoPhaseDispatches(events) {
        // 其实就是对 events 这个数组里面,它的每一个节点去调用这个方法
        forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
      }
      
      function accumulateTwoPhaseDispatchesSingle(event) {
        // 存在 phasedRegistrationNames 则调用 traverseTwoPhase
        if (event && event.dispatchConfig.phasedRegistrationNames) {
          traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
        }
      }
      
      // 在 event 对象上 插入 listener 的过程
      function accumulateDirectionalDispatches(inst, phase, event) {
        // 忽略
        if (__DEV__) {
          warningWithoutStack(inst, 'Dispatching inst must not be null');
        }
        // 获取 listener
        const listener = listenerAtPhase(inst, event, phase);
        if (listener) {
          // 注意这里 event._dispatchListeners 和 下面的 event._dispatchInstances 保持两者一一对应的关系
          event._dispatchListeners = accumulateInto(
            event._dispatchListeners,
            listener,
          );
          event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
        }
      }
      
      function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
        const registrationName =
          event.dispatchConfig.phasedRegistrationNames[propagationPhase];
        return getListener(inst, registrationName);
      }
      
      // packages/events/EventPluginHub.js#L126
      export function getListener(inst: Fiber, registrationName: string) {
        let listener;
      
        // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
        // live here; needs to be moved to a better place soon
        const stateNode = inst.stateNode;
        if (!stateNode) {
          // Work in progress (ex: onload events in incremental mode).
          return null;
        }
        const props = getFiberCurrentPropsFromNode(stateNode); // 从 dom tree上获取 props
        if (!props) {
          // Work in progress.
          return null;
        }
        listener = props[registrationName];
        if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
          return null;
        }
        invariant(
          !listener || typeof listener === 'function',
          'Expected `%s` listener to be a function, instead got a value of `%s` type.',
          registrationName,
          typeof listener,
        );
        return listener;
      }
      
      // packages/shared/ReactTreeTraversal.js#L86
      export function traverseTwoPhase(inst, fn, arg) {
        const path = [];
        // 找到所有上层节点,并存入 path
        while (inst) {
          path.push(inst);
          inst = getParent(inst);
        }
        // 下面是核心,执行两个阶段的回调,捕获和冒泡
        let i;
        for (i = path.length; i-- > 0; ) { // 注意这个 i 的顺序
          // path[i]:节点, arg:event
          fn(path[i], 'captured', arg); // captured 是从 window 向下触发的
        }
        for (i = 0; i < path.length; i++) { // 注意这个 i 的顺序
          fn(path[i], 'bubbled', arg); // bubbled 是从下向 window 方向的
        }
        // 基于上面两个 循环
        // 这样的话,就不需要在 event 对象上面单独维护 capture 的这个事件的它的一个数组
        // 还有 bubble 的事件的一个速度,只需要放在同一个数组里面,然后按照这个数组的顺序去触发就可以了
      }
      // packages/shared/ReactTreeTraversal.js#L10
      function getParent(inst) {
        do {
          inst = inst.return;
          // TODO: If this is a HostRoot we might want to bail out.
          // That is depending on if we want nested subtrees (layers) to bubble
          // events to their parent. We could also go through parentNode on the
          // host node but that wouldn't work for React Native and doesn't let us
          // do the portal feature.
        } while (inst && inst.tag !== HostComponent);
        if (inst) {
          return inst; // 返回的 inst 是一个 HostComponent
        }
        return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
  • 以上,生产 event 对象,然后去挂载它的事件,这个过程是非常的复杂的

  • react 团队把整个事件系统去重新抽象的这么一个过程,而且设计的超级复杂

  • 这一套东西只是非常适合react,在其他框架要使用这类event库,会有很大的成本

  • 目前为止,通过 ChangeEventPlugin 来了解了整个 event 对象的处理过程

  • 后续其他的类似事件的处理逻辑到后面都是一样的

  • 但每一个 plugin 或多或少有一些自己的一些区别,这里不再赘述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/65815
推荐阅读
相关标签
  

闽ICP备14008679号