<
>

React合成事件原理解析

2022-07-06 15:46:03 来源:易采站长站 作者:

目录
事件介绍什么是事件?举个栗子代码实现React合成事件基础知识什么是合成事件?在React中事件的写法和原生事件写法的区别?为什么会有合成事件?合成事件机制简述React合成事件实现原理事件注册事件触发-事件监听器做了什么React中模拟冒泡和捕获总结

事件介绍

什么是事件?

事件是在编程时系统内发生的动作或者发生的事情,而开发者可以某种方式对事件做出回应,而这里有几个先决条件

    事件对象给事件对象注册事件,当事件被触发后需要做什么事件触发

    举个栗子

    ????在机场等待检票,听到广播后排队检票

      事件对象,也就是检票事件事件注册/监听,也就是我们在机场等待的时候事件触发,广播响起开始检票事件触发之后的回调,我们开始排队...

      代码实现

      // 检票事件
      var event = new Event('check-ticket');
      // 事件注册,并编写回调
      elem.addEventListener('check-ticket', function (e) { 
        // 开始排队
        LiningUp()
      });
      // 事件触发,广播开始检票
      elem.dispatchEvent(event);

      React合成事件基础知识

      什么是合成事件?

      不是浏览器本身触发的事件,自己创建和触发的事件。

      在React中事件的写法和原生事件写法的区别?

        React>使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
        // HTML中的写法
        <button onclick="handleClick()">
          Activate Lasers
        </button>
        // React中的写法
        <button onClick={handleClick}>
          Activate Lasers
        </button>

        为什么会有合成事件?

          对事件进行归类,可以在事件产生的任务上包含不同的优先级提供合成事件对象,抹平浏览器的兼容性差异

          合成事件机制简述

          提供了一种“顶层注册,事件收集,统一触发”的事件机制

            “顶层注册”,其实是在root元素上绑定一个统一的事件处理函数“事件收集”,>“统一触发”,在收集过程之后,对收集的事件逐一执行,并共享同一个合成事件对象

            React合成事件实现原理

            事件注册

            时机:在fiber节点进入render阶段的complete阶段时,名为onClick的props会被识别为事件进行处理

            function setInitialDOMProperties(
              tag: string,
              domElement: Element,
              rootContainerElement: Element | Document,
              nextProps: Object,
              isCustomComponentTag: boolean,
            ): void {
              for (const propKey in nextProps) {
                if (!nextProps.hasOwnProperty(propKey)) {
                  ...
                } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
                    // 如果propKey属于事件类型,则进行事件绑定
                    ensureListeningTo(rootContainerElement, propKey, domElement);
                  }
                }
              }
            }
            // registrationNameDependencies为合成事件名和真实事件的映射,格式如下:
            {
              onChange: ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']
              onChangeCapture: ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']
              onClick: ['click']
            }

            调用ensureListeningTo进行事件绑定

            然后调用DOMPluginEventSystem.js中的addTrappedEventListener进行事件注册,而监听器listener是React通过createEventListenerWrapperWithPriority创建出优先级不同的时间监听包装器

            总的来说,会有三种事件监听包装器:

              dispatchDiscreteEvent: 处理离散事件dispatchUserBlockingUpdate:处理用户阻塞事件dispatchEvent:处理连续事件

              事件触发-事件监听器做了什么

              负责以不同的优先级权重来触发真正的事件流程

              createEventListenerWrapperWithPriority中,根据eventPriority事件优先级不同调用事件dispatchDiscreteEventdispatchUserBlockingUpdatedispatchUserBlockingUpdatedispatchEvent进行事件绑定

              export function createEventListenerWrapperWithPriority(
                targetContainer: EventTarget,
                domEventName: DOMEventName,
                eventSystemFlags: EventSystemFlags,
              ): Function {
                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,
                );
              }

              在每一个事件绑定函数中,会调用dispatchEventsForPlugins函数

                合成事件,放入dispatchQueue中

                而dispatchQueue由两部分组成

                  合成事件对象多个listeners(可以共用一个合成事件对象)

                  执行事件执行路径下的事件,从dispatchQueue中取出事件对象events和具体执行路径listeners,然后遍历执行

                  function dispatchEventsForPlugins(
                    domEventName: DOMEventName,
                    eventSystemFlags: EventSystemFlags,
                    nativeEvent: AnyNativeEvent,
                    targetInst: null | Fiber,
                    targetContainer: EventTarget,
                  ): void {
                    const nativeEventTarget = getEventTarget(nativeEvent);
                    const dispatchQueue: DispatchQueue = [];
                    // 通过不同事件类型插件进行事件对象合成,将合成事件放入事件队列中:dispatchQueue中
                    extractEvents(
                      dispatchQueue,
                      domEventName,
                      targetInst,
                      nativeEvent,
                      nativeEventTarget,
                      eventSystemFlags,
                      targetContainer,
                    );
                    // 执行dispatchQueue中具体事件执行路径下的事件
                    processDispatchQueue(dispatchQueue, eventSystemFlags);
                  }
                  // 通过extractEvents合成的dispatchQueue的格式如下
                  [
                    {
                      // event是合成事件对象
                      event, 
                      // 具体的事件执行路径,currentTarget、listener等信息
                      listeners: [
                        {
                          currentTarget: div.counter,
                          instance: FiberNode {tag: 5, key: null, elementType: 'div', type: 'div', stateNode: div.counter, …},
                          listener: e => {…}
                        },
                        {
                          currentTarget: div.counter-parent,
                          instance: FiberNode {tag: 5, key: null, elementType: 'div', type: 'div', stateNode: div.counter, …},
                          listener: e => {…}
                        }
                      ] 
                    }
                  ]

                  React中模拟冒泡和捕获

                  原理:收集的事件放在dispatchQueue数组中,而冒泡和捕获的区别在于执行时机和顺序,那么我们只需要对数组按照不同顺序循环执行即可

                  function processDispatchQueueItemsInOrder(
                    event: ReactSyntheticEvent,
                    dispatchListeners: Array<DispatchListener>,
                    inCapturePhase: boolean,
                  ): void {
                    let previousInstance;
                  ​
                    if (inCapturePhase) {
                      // 事件捕获倒序循环
                      for (let i = dispatchListeners.length - 1; i >= 0; i--) {
                        const {instance, currentTarget, listener} = dispatchListeners[i];
                        if (instance !== previousInstance && event.isPropagationStopped()) {
                          return;
                        }
                        // 执行事件,传入event对象,和currentTarget
                        executeDispatch(event, listener, currentTarget);
                        previousInstance = instance;
                      }
                    } else {
                      // 事件冒泡正序循环
                      for (let i = 0; i < dispatchListeners.length; i++) {
                        const {instance, currentTarget, listener} = dispatchListeners[i];
                        // 如果事件对象阻止了冒泡,则return掉循环过程
                        if (instance !== previousInstance && event.isPropagationStopped()) {
                          return;
                        }
                        executeDispatch(event, listener, currentTarget);
                        previousInstance = instance;
                      }
                    }
                  }

                  总结

                  以上就是最近学习React源码之--合成事件的知识整理啦!

                  首先会在fiber节点进入render阶段的complete阶段时,将事件监听绑定在root上。然后调用ensureListeningTo进行事件绑定,生成事件合成对象、收集事件、触发真正的事件。

暂时禁止评论

微信扫一扫

易采站长站微信账号