• react源码中的生命周期和事件系统


    这一章我想跟大家探讨的是React生命周期事件系统

    jsx的编译结果

    在这里插入图片描述

    因为前面也讲到jsxv17中的编译结果,除了标签名,其他的挂在标签上的属性(比如class),事件(比如click事件),都是放在_jsxRuntime.jsx函数的第二参数上。表现为key:value的形式,这里我们就会产生几个问题。

    • react是怎么知道函数体(事件处理函数)是什么的呢?
    • react又是在什么阶段去处理这些事件的呢?

    这里我们先卖个关子,我们先来看看一个完整的React应用的完整的生命周期是怎么样的,我们都知道React分为类组件函数组件,两种组件的部分生命周期函数发生了一些变化,在这里我会分别对两种组件的生命周期做讲解。

    React组件的生命周期

    组件挂载的时候的执行顺序

    因为在_jsxRuntime.jsx编译jsx对象的时候,我们会去做处理defaultPropspropType静态类型检查。所以这也算是一个生命周期吧。Class组件具有单独的constructor,在mount阶段会去执行这个构造函数,我曾经做了部分研究,这个constructor是类组件独有的,还是class独有的?后来发现这个constructorclass独有的,怎么理解这句话呢?

    • 在《重学ES6》这本书中提到:ES6中新增了类的概念,一个类必须要有constructor方法,如果在类中没有显示定义,则一个空的constructor方法会被默认添加。对于ReactClassComponent来讲需要constructor的作用就是用来初始化state和绑定事件,另外一点就是声明了constructor,就必须要调用super,我们一般用来接收props传递。假如你不写constructor,那就没法用props了,当然了要在constructor中使用props,也必须用super接收才行。
    • 所以对于类组件来讲的话,constructor也算是一个生命周期钩子。

    getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

    render被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

    • React 元素。通常通过 JSX 创建。例如,
      会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是
      还是 均为 React 元素。
    • 数组或 fragments。 使得 render 方法可以返回多个元素。
    • Portals。可以渲染子节点到不同的 DOM 子树中。
    • 字符串或数值类型。它们在 DOM 中会被渲染为文本节点。
    • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && 的模式,其中 test 为布尔类型。)

    componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。在这里适合去发送异步请求

    组件更新的时候的执行顺序

    getDerivedStateFromProps => shouldComponentUpdate() => render() => getSnapshotBeforeUpdate() => componentDidUpdate()

    • 其中shouldComponentUpdate也被称作为性能优化的一种钩子,其作用在于比较两次更新的stateprops是否发生变化,决定是否更新当前组件,比较的方式是浅比较,以前讲过这里不再复述。
    • getSnapshotBeforeUpdate函数在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息。此生命周期方法的任何返回值将作为参数传递给componentDidUpdate()
    • componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
    组件卸载的时候执行顺序

    componentWillUnmount() 会在组件卸载销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除timer取消网络请求等等。

    组件在发生错误的时候执行顺序

    getDerivedStateFromError => componentDidCatch 关于这两个钩子,同学们可自行移步官网。

    当然上面的只是ClassComponent的生命周期执行顺序,而在新版本的React中已经删除掉了componentDidMountcomponentDidUpdatecomponentWillUnMount,取而代之的是useEffectuseLayoutEffect。那究竟是谁代替了他们三个呢?这个问题我已经在React源码解析系列(八) – 深入hooks的原理 中阐述过了,这里不再复述。

    现在来回答第一个问题:react是怎么知道函数体是什么的呢? 这个问题其实问的非常好,babel解析后的jsx本身只会去关注{事件名:函数名},但是每一个事件都是需要被注册、绑定的,然后通过事件触发,来执行绑定函数的函数体。解释这种问题还是得要去看一下源码里面的具体实现。

    listenToAllSupportedEvents

    我们在React源码解析系列(二) – 初始化组件的创建更新流程中提到rootFiberFiberRoot的创建,创建完毕之后我们就需要去创建事件,创建事件的入口函数为listenToAllSupportedEvents

    // packages/react-dom/src/events/DOMPluginEventSystem.js
    export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
      if (enableEagerRootListeners) { // enableEagerRootListeners默认值为false
    
        // listeningMarker就是一个随机数+字符串,作为唯一值
        if (rootContainerElement[listeningMarker]) {
          ...
          return;
        }
        rootContainerElement[listeningMarker] = true;
    
        // 遍历allNativeEvents的所有事件
        allNativeEvents.forEach(domEventName => {
    
          // 如果不是委托事件,没有冒泡阶段
          // nonDelegatedEvents全部媒体事件,
          if (!nonDelegatedEvents.has(domEventName)) {
            listenToNativeEvent(
              domEventName,
              false,
              ((rootContainerElement: any): Element),
              null,
            );
          }
          // 有冒泡阶段
          listenToNativeEvent(
            domEventName,
            true,
            ((rootContainerElement: any): Element),
            null,
          );
        });
      }
    }
    
    //listeningMarker
    // 唯一标识
    const listeningMarker =
      '_reactListening' +
      Math.random()
        .toString(36)
        .slice(2);
    
    • 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

    我们在这里必须要关注一下allNativeEvents是什么东西,allNativeEvents在源码里体现为一个存储着事件名的Set结构:

    export const allNativeEvents: Set<DOMEventName> = new Set();
    
    • 1

    接下来看看listenToNativeEvent究竟干了些什么。

    相关参考视频讲解:进入学习

    listenToNativeEvent

    export function listenToNativeEvent(
      domEventName: DOMEventName,// 事件名
      isCapturePhaseListener: boolean, // 根据上个函数,这里应该是确定是是能够冒泡的事件
      rootContainerElement: EventTarget,  targetElement: Element | null,  eventSystemFlags?: EventSystemFlags = 0, // 事件标记
    ): void {
    
      let target = rootContainerElement;
    
      //如果是selectionchange事件,加到dom上
      if (
        domEventName === 'selectionchange' &&
        (rootContainerElement: any).nodeType !== DOCUMENT_NODE
      ) {
        target = (rootContainerElement: any).ownerDocument;
      }
    
      if (
        targetElement !== null &&
        !isCapturePhaseListener &&
        nonDelegatedEvents.has(domEventName) // 非冒泡事件
      ) {
       ...
    
        //滚动事件不冒泡
        if (domEventName !== 'scroll') {
          return;
        }
    
        eventSystemFlags |= IS_NON_DELEGATED; // is_non_delegated 不是委托事件
        target = targetElement;
      }
      //获取dom上绑定的事件名数组 Set[] || 
      const listenerSet = getEventListenerSet(target);
      // 处理事件名为捕获阶段与冒泡阶段 Set[click_bubble]
      const listenerSetKey = getListenerSetKey(
        domEventName,
        isCapturePhaseListener,
      );
    
      // 把没有打过的IS_CAPTURE_PHASE的符合条件的事件,打上标签
      if (!listenerSet.has(listenerSetKey)) {
        if (isCapturePhaseListener) {
          // 打上捕获的标签
          eventSystemFlags |= IS_CAPTURE_PHASE;
        }
    
        // 往节点上添加事件绑定
        addTrappedEventListener(
          target,
          domEventName,
          eventSystemFlags,
          isCapturePhaseListener,
        );
    
        // 往listenerSet中添加事件名
        listenerSet.add(listenerSetKey);
      }
    }
    
    //getEventListenerSet
    export function getEventListenerSet(node: EventTarget): Set<string> {
      let elementListenerSet = (node: any)[internalEventHandlersKey];
    
      if (elementListenerSet === undefined) {
        // 创建一个Set来存放事件名
        elementListenerSet = (node: any)[internalEventHandlersKey] = new Set();
      }
      return elementListenerSet;
    }
    
    // getListenerSetKey
    export function getListenerSetKey(
      domEventName: DOMEventName,  capture: boolean,
    ): string {
      // capture捕获,bubble冒泡
      return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
    }
    
    // addTrappedEventListener
    function addTrappedEventListener(
      targetContainer: EventTarget, // 容器
      domEventName: DOMEventName, // 事件名
      eventSystemFlags: EventSystemFlags, //事件名标识
      isCapturePhaseListener: boolean, // 事件委托
      isDeferredListenerForLegacyFBSupport?: boolean, 
    ) {
    
      // 创建具有优先级的事件监听函数,返回值为function
      let listener = createEventListenerWrapperWithPriority(
        targetContainer,
        domEventName,
        eventSystemFlags,
      );
    
      ...
    
      targetContainer =
        enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
          ? (targetContainer: any).ownerDocument
          : targetContainer;
    
      let unsubscribeListener;
      ...
    
      // 区分捕获、冒泡 通过node.addEventListener绑定事件到节点上
      if (isCapturePhaseListener) {
        if (isPassiveListener !== undefined) {
          unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
            targetContainer,
            domEventName,
            listener,
            isPassiveListener,
          );
        } else {
          unsubscribeListener = addEventCaptureListener(
            targetContainer,
            domEventName,
            listener,
          );
        }
      } else {
        if (isPassiveListener !== undefined) {
          unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
            targetContainer,
            domEventName,
            listener,
            isPassiveListener,
          );
        } else {
          unsubscribeListener = addEventBubbleListener(
            targetContainer,
            domEventName,
            listener,
          );
        }
      }
    }
    
    // createEventListenerWrapperWithPriority
    export function createEventListenerWrapperWithPriority(
      targetContainer: EventTarget, // 容器
      domEventName: DOMEventName, // 事件名
      eventSystemFlags: EventSystemFlags, //标识
    ): Function {
    
      // 获取事件Map里面已经标记好的优先级
      const eventPriority = getEventPriorityForPluginSystem(domEventName);
    
      let listenerWrapper;
      // 根据优先级不同绑定不同的执行函数
      switch (eventPriority) {
        //离散事件
        case DiscreteEvent:
          listenerWrapper = dispatchDiscreteEvent;
          break;
        // 用户交互阻塞渲染的事件 
        case UserBlockingEvent:
          listenerWrapper = dispatchUserBlockingUpdate;
          break;
        // 其他事件
        case ContinuousEvent:
        // 默认事件
        default:
          listenerWrapper = dispatchEvent;
          break;
      }
    
      return listenerWrapper.bind(
        null,
        domEventName,
        eventSystemFlags,
        targetContainer,
      );
    }
    
    • 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
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174

    在这里我们关注一下获取优先级getEventPriorityForPluginSystem这里,你会不会产生一个疑问,React内部事件我们知道React本身一定会给优先级的,但是非React事件呢,比如原生事件,他们的优先级是怎么确定的呢?不要急,我们看一看就知道了。

    getEventPriorityForPluginSystem

    export function getEventPriorityForPluginSystem(
      domEventName: DOMEventName,
    ): EventPriority {
      // 通过事件名获取优先级
      const priority = eventPriorities.get(domEventName);
    
      // ContinuousEvent为默认优先级 
      return priority === undefined ? ContinuousEvent : priority;
    }
    
    //eventPriorities
    const eventPriorities = new Map();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    eventPriorities本身是一个Map结构,我们可以发现两个地方进行了eventPriorities.set()的操作。

    // packages/react-dom/src/events/DOMEventProperties.js
    function setEventPriorities(
      eventTypes: Array<DOMEventName>,  priority: EventPriority,
    ): void {
      for (let i = 0; i < eventTypes.length; i++) {
        // 往eventPriorities添加优先级
        eventPriorities.set(eventTypes[i], priority);
      }
    }
    
    //registerSimplePluginEventsAndSetTheirPriorities
    function registerSimplePluginEventsAndSetTheirPriorities(
      eventTypes: Array<DOMEventName | string>,  priority: EventPriority,
    ): void {
      for (let i = 0; i < eventTypes.length; i += 2) {
        const topEvent = ((eventTypes[i]: any): DOMEventName);
        const event = ((eventTypes[i + 1]: any): string);
        const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
        // 改变事件名 click => onClick
        const reactName = 'on' + capitalizedEvent;
        // 往eventPriorities添加优先级
        eventPriorities.set(topEvent, priority);
        topLevelEventsToReactNames.set(topEvent, reactName);
    
        // 注册捕获阶段,冒泡阶段的事件
        registerTwoPhaseEvent(reactName, [topEvent]);
      }
    }
    
    • 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

    这就说明,在这两个函数里面已经做好了优先级的处理,那我们可以去看一下在哪里调用的这两个函数,我们发现在函数registerSimpleEvents中,执行了这两个函数,往eventPriorities里面添加优先级。

    // packages/react-dom/src/events/DOMEventProperties.js
    export function registerSimpleEvents() {
      // 处理离散事件优先级
      registerSimplePluginEventsAndSetTheirPriorities(
        discreteEventPairsForSimpleEventPlugin,
        DiscreteEvent,
      );
      // 处理用户阻塞事件优先级
      registerSimplePluginEventsAndSetTheirPriorities(
        userBlockingPairsForSimpleEventPlugin,
        UserBlockingEvent,
      );
      // 处理默认事件优先级
      registerSimplePluginEventsAndSetTheirPriorities(
        continuousPairsForSimpleEventPlugin,
        ContinuousEvent,
      );
      // 处理其他事件优先级
      setEventPriorities(otherDiscreteEvents, DiscreteEvent);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述代码中可以看到有非常多的Plugin,代码如下:

    const discreteEventPairsForSimpleEventPlugin = [
      ('cancel': DOMEventName), 'cancel',
      ('click': DOMEventName), 'click',
      ('close': DOMEventName), 'close',
      ('contextmenu': DOMEventName), 'contextMenu',
      ('copy': DOMEventName), 'copy',
      ('cut': DOMEventName), 'cut',
      ('auxclick': DOMEventName), 'auxClick',
      ('dblclick': DOMEventName), 'doubleClick', // Careful!
      ('dragend': DOMEventName), 'dragEnd',
      ('dragstart': DOMEventName), 'dragStart',
      ('drop': DOMEventName), 'drop',
      ('focusin': DOMEventName), 'focus', // Careful!
      ('focusout': DOMEventName), 'blur', // Careful!
      ('input': DOMEventName), 'input',
      ('invalid': DOMEventName), 'invalid',
      ('keydown': DOMEventName), 'keyDown',
      ('keypress': DOMEventName), 'keyPress',
      ('keyup': DOMEventName), 'keyUp',
      ('mousedown': DOMEventName), 'mouseDown',
      ('mouseup': DOMEventName), 'mouseUp',
      ('paste': DOMEventName), 'paste',
      ('pause': DOMEventName), 'pause',
      ('play': DOMEventName), 'play',
      ('pointercancel': DOMEventName), 'pointerCancel',
      ('pointerdown': DOMEventName), 'pointerDown',
      ('pointerup': DOMEventName), 'pointerUp',
      ('ratechange': DOMEventName), 'rateChange',
      ('reset': DOMEventName), 'reset',
      ('seeked': DOMEventName), 'seeked',
      ('submit': DOMEventName), 'submit',
      ('touchcancel': DOMEventName), 'touchCancel',
      ('touchend': DOMEventName), 'touchEnd',
      ('touchstart': DOMEventName), 'touchStart',
      ('volumechange': DOMEventName), 'volumeChange',
    ];
    
    const otherDiscreteEvents: Array<DOMEventName> = [
      'change',
      'selectionchange',
      'textInput',
      'compositionstart',
      'compositionend',
      'compositionupdate',
    ];
    
    const userBlockingPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
      ('drag': DOMEventName), 'drag',
      ('dragenter': DOMEventName), 'dragEnter',
      ('dragexit': DOMEventName), 'dragExit',
      ('dragleave': DOMEventName), 'dragLeave',
      ('dragover': DOMEventName), 'dragOver',
      ('mousemove': DOMEventName), 'mouseMove',
      ('mouseout': DOMEventName), 'mouseOut',
      ('mouseover': DOMEventName), 'mouseOver',
      ('pointermove': DOMEventName), 'pointerMove',
      ('pointerout': DOMEventName), 'pointerOut',
      ('pointerover': DOMEventName), 'pointerOver',
      ('scroll': DOMEventName), 'scroll',
      ('toggle': DOMEventName), 'toggle',
      ('touchmove': DOMEventName), 'touchMove',
      ('wheel': DOMEventName), 'wheel',
    ];
    
    const continuousPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
      ('abort': DOMEventName), 'abort',
      (ANIMATION_END: DOMEventName), 'animationEnd',
      (ANIMATION_ITERATION: DOMEventName), 'animationIteration',
      (ANIMATION_START: DOMEventName), 'animationStart',
      ('canplay': DOMEventName), 'canPlay',
      ('canplaythrough': DOMEventName), 'canPlayThrough',
      ('durationchange': DOMEventName), 'durationChange',
      ('emptied': DOMEventName), 'emptied',
      ('encrypted': DOMEventName), 'encrypted',
      ('ended': DOMEventName), 'ended',
      ('error': DOMEventName), 'error',
      ('gotpointercapture': DOMEventName), 'gotPointerCapture',
      ('load': DOMEventName), 'load',
      ('loadeddata': DOMEventName), 'loadedData',
      ('loadedmetadata': DOMEventName), 'loadedMetadata',
      ('loadstart': DOMEventName), 'loadStart',
      ('lostpointercapture': DOMEventName), 'lostPointerCapture',
      ('playing': DOMEventName), 'playing',
      ('progress': DOMEventName), 'progress',
      ('seeking': DOMEventName), 'seeking',
      ('stalled': DOMEventName), 'stalled',
      ('suspend': DOMEventName), 'suspend',
      ('timeupdate': DOMEventName), 'timeUpdate',
      (TRANSITION_END: DOMEventName), 'transitionEnd',
      ('waiting': DOMEventName), 'waiting',
    ];
    
    • 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

    而在registerSimplePluginEventsAndSetTheirPriorities函数里面,我们发现了注册事件registerTwoPhaseEvent,我们继续去深究一下,究竟是怎么注册的。

    registerTwoPhaseEvent

    export function registerTwoPhaseEvent(
      registrationName: string, // 注册事件reactName
      dependencies: Array<DOMEventName>, // 依赖
    ): void {
      registerDirectEvent(registrationName, dependencies);
      registerDirectEvent(registrationName + 'Capture', dependencies);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    registerDirectEvent

    // Mapping from registration name to event name
    export const registrationNameDependencies = {};
    
    export function registerDirectEvent(
      registrationName: string, //react事件名onClick
      dependencies: Array<DOMEventName>, // 依赖
    ) {
      ...
    
      // 以react事件名为key,dependencies为value的map对象
      registrationNameDependencies[registrationName] = dependencies;
    
      if (__DEV__) {
        ...
      }
    
      // 遍历依赖,把每一项加入到allNativeEvents中去
      for (let i = 0; i < dependencies.length; i++) {
        allNativeEvents.add(dependencies[i]);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    前面说allNativeEvents是一个存储事件名的Set,这里往里面添加事件名,就完成了事件注册。还没有完,上面说过了事件注册,与事件绑定,但是用户点击的时候,应该怎么去触发呢?上面的代码,在获取了优先级之后,每个事件会根据当前优先级生成一个listenerWrapper,这个listenerWrapper也就是对应的事件触发绑定的函数。dispatchDiscreteEventdispatchUserBlockingUpdatedispatchEvent三个函数都通过bind执行,我们都知道bind绑定的函数,会返回一个新函数,并不会立即执行。所以我们也必须看看他的入参是什么。

    • thisnull
    • argmentsdomEventName:事件名,eventSystemFlags:事件类型标记,targetContainer:目标容器。

    dispatchEvent

    因为不管是dispatchDiscreteEventdispatchUserBlockingUpdate最后都会去执行dispatchEvent,所以我们可以看看他的实现。

    // packages/react-dom/src/events/ReactDOMEventListener.js
    export function dispatchEvent(
      domEventName: DOMEventName, // 事件名
      eventSystemFlags: EventSystemFlags, // 事件类型标记
      targetContainer: EventTarget, // 目标容器
      nativeEvent: AnyNativeEvent, // native事件
    ): void {
      ...
      // 如果被阻塞了,尝试调度事件 并返回挂载的实例或者容器
      const blockedOn = attemptToDispatchEvent(
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      );
    
      if (blockedOn === null) {
        // We successfully dispatched this event.
        ...
        return;
      }
    
      ...
    
      // 调度事件,触发事件
      dispatchEventForPluginEventSystem(
        domEventName,
        eventSystemFlags,
        nativeEvent,
        null,
        targetContainer,
      );
    }
    
    // dispatchEventForPluginEventSystem
    export function dispatchEventForPluginEventSystem(
      domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,  nativeEvent: AnyNativeEvent,  targetInst: null | Fiber,  targetContainer: EventTarget,
    ): void {
      ...
      //批量更新事件 
      batchedEventUpdates(() =>
        dispatchEventsForPlugins(
          domEventName,
          eventSystemFlags,
          nativeEvent,
          ancestorInst,
          targetContainer,
        ),
      );
    }
    
    // batchedEventUpdates
    export function batchedEventUpdates(fn, a, b) {
      ...
      isBatchingEventUpdates = true;
      try {
        // fn : ()=>dispatchEventsForPlugins
        //(domEventName,eventSystemFlags,ativeEvent,ancestorInst,targetContainer,),
        // a: undefined
        // b: undefined
        return batchedEventUpdatesImpl(fn, a, b); 
        // batchedEventUpdatesImpl(fn, a, b) =>
        // Defaults
        // let batchedUpdatesImpl = function(fn, bookkeeping) {
        // return fn(bookkeeping); 执行dispatchEventsForPlugins
    };
      } finally {
        ...
      }
    }
    
    • 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

    dispatchEventsForPlugins

    function dispatchEventsForPlugins(
      domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,  nativeEvent: AnyNativeEvent,  targetInst: null | Fiber,  targetContainer: EventTarget,
    ): void {
      const nativeEventTarget = getEventTarget(nativeEvent);
      const dispatchQueue: DispatchQueue = [];
      //创建合成事件,遍历fiber链表,将会触发的事件加入到dispatchQueue中
      extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        eventSystemFlags,
        targetContainer,
      );
      //触发时间队列,执行事件
      processDispatchQueue(dispatchQueue, eventSystemFlags);
    }
    
    //extractEvents
    function extractEvents(
      dispatchQueue: DispatchQueue,  domEventName: DOMEventName,  targetInst: null | Fiber,  nativeEvent: AnyNativeEvent,  nativeEventTarget: null | EventTarget,  eventSystemFlags: EventSystemFlags,  targetContainer: EventTarget,
    ) {
      ...
      let from;
      let to;
      ...
    
      const leave = new SyntheticEventCtor(
        leaveEventType,
        eventTypePrefix + 'leave',
        from,
        nativeEvent,
        nativeEventTarget,
      );
      leave.target = fromNode;
      leave.relatedTarget = toNode;
    
      let enter: KnownReactSyntheticEvent | null = null;
      ...
      accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
    }
    
    //accumulateEnterLeaveTwoPhaseListeners
    export function accumulateEnterLeaveTwoPhaseListeners(
      dispatchQueue: DispatchQueue,  leaveEvent: KnownReactSyntheticEvent,  enterEvent: null | KnownReactSyntheticEvent,  from: Fiber | null,  to: Fiber | null,
    ): void {
      const common = from && to ? getLowestCommonAncestor(from, to) : null;
    
      if (from !== null) {
        accumulateEnterLeaveListenersForEvent(
          dispatchQueue,
          leaveEvent,
          from,
          common,
          false,
        );
      }
      if (to !== null && enterEvent !== null) {
        accumulateEnterLeaveListenersForEvent(
          dispatchQueue,
          enterEvent,
          to,
          common,
          true,
        );
      }
    }
    
    // accumulateEnterLeaveListenersForEvent
    function accumulateEnterLeaveListenersForEvent(
      dispatchQueue: DispatchQueue,  event: KnownReactSyntheticEvent,  target: Fiber,  common: Fiber | null,  inCapturePhase: boolean,
    ): void {
      // 获取注册的事件名
      const registrationName = event._reactName;
      // 事件处理函数容器
      const listeners: Array<DispatchListener> = [];
      //节点实例
      let instance = target;
    
      // 遍历fiber,获取fiber上的事件对应的事件处理函数
      while (instance !== null) {
    
        if (instance === common) {
          break;
        }
    
        const {alternate, stateNode, tag} = instance;
        if (alternate !== null && alternate === common) {
          break;
        }
    
        if (tag === HostComponent && stateNode !== null) {
          const currentTarget = stateNode;
    
          // 根据捕获阶段,还是冒泡阶段处理不同的函数逻辑
          if (inCapturePhase) {
            const captureListener = getListener(instance, registrationName);
            if (captureListener != null) {
    
              // 加入到listeners中
              // instance:当前fiebr实例
              // currentTarget:当前dom
              listeners.unshift(
                createDispatchListener(instance, captureListener, currentTarget),
              );
            }
          } else if (!inCapturePhase) {
            // 冒泡
            const bubbleListener = getListener(instance, registrationName);
            if (bubbleListener != null) {
              // 加入到listeners中
              listeners.push(
                createDispatchListener(instance, bubbleListener, currentTarget),
              );
            }
          }
        }
        // 当前fiber实例的父级
        instance = instance.return;
      }
      if (listeners.length !== 0) {
        // 把事件、事件处理函数全部推到dispatchQueue中
        dispatchQueue.push({event, listeners});
      }
    }
    // processDispatchQueue
    export function processDispatchQueue(
      dispatchQueue: DispatchQueue, // 事件队列
      eventSystemFlags: EventSystemFlags, // 事件类型标记
    ): void {
      const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
    
      for (let i = 0; i < dispatchQueue.length; i++) {
        const {event, listeners} = dispatchQueue[i];
        // 执行事件,完成触发
        processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
        //  event system doesn't use pooling.
      }
      // This would be a good time to rethrow if any of the event handlers threw.
      rethrowCaughtError();
    }
    
    • 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
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142

    所以到这里,React的事件系统就解析完了,在这里上面的问题就很好解答了,React对事件名与事件处理函数对做了绑定,并在创建rootFiber的时候就做了事件注册事件绑定事件调度。那么他们的执行流程大致如下:

    在这里插入图片描述

    总结

    这一章主要是介绍组件在mountupdatedestroy阶段的生命周期执行顺序与React事件系统的注册,绑定,调度更新等

  • 相关阅读:
    Django基础入门操作 (Django-01)
    高通8295中国首发!智舱再度上演「军备竞赛」,这次有何不同
    【java毕业设计】基于java+swing+Eclipse的俄罗斯方块游戏GUI设计与实现(毕业论文+程序源码)——俄罗斯方块游戏
    【Java 进阶篇】用JSTL玩转Java:了解JSTL常用标签
    LuatOS-SOC接口文档(air780E)--nbiot - NB-IOT操作库
    springcloud+nacos+dubbo服务器部署问题
    HarmonyOS 应用生命周期有哪些? 按返回键会调用哪些生命周期?
    从0编写区块链:用python解释区块链最基本原理
    C语言 预处理详解
    【Android -- 数据存储】使用文件存储数据
  • 原文地址:https://blog.csdn.net/weixin_59558923/article/details/127993278