首页前端开发JavaScriptReact事件机制源码解析

React事件机制源码解析

时间2024-02-01 08:36:03发布访客分类JavaScript浏览578
导读:收集整理的这篇文章主要介绍了React事件机制源码解析,觉得挺不错的,现在分享给大家,也给大家做个参考。 目录原理简述源码浅析委托事件绑定listenToAllSupportedEven...
收集整理的这篇文章主要介绍了React事件机制源码解析,觉得挺不错的,现在分享给大家,也给大家做个参考。
目录
  • 原理简述
  • 源码浅析
    • 委托事件绑定
      • listenToAllSupportedEvents
      • listenToNativeEvent
      • addTrappedEventListener
    • 不需要委托事件绑定
      • setInitialProperties
      • listenToNonDelegatedEvent
    • 事件处理函数
      • createEventListenerWrapperWithPriority
      • dispatchEvent
      • dispatchEventsForPlugins
      • extractEvents
      • accumulateSinglePhaseListeners
      • processDispatchQueue
      • processDispatchQueueItemsInOrder
      • executeDispatch
  • 结语
    • 小思考

    React v17里事件机制有了比较大的改动,想来和v16差别还是比较大的。

    本文浅析的React版本为17.0.1,使用ReactDOM.render创建应用,不含优先级相关。

    原理简述

    React中事件分为委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot创建的时候,就会在root节点的DOM元素上绑定几乎所有事件的处理函数,而不需要委托事件只会将处理函数绑定在DOM元素本身。

    同时,React将事件分为3种类型——discreteEvent、userBlockingEvent、continuousEvent,它们拥有不同的优先级,在绑定事件处理函数时会使用不同的回调函数。

    React事件建立在原生基础上,模拟了一套冒泡和捕获的事件机制,当某一个DOM元素触发事件后,会冒泡到React绑定在root节点的处理函数,通过target获取触发事件的DOM对象和对应的Fiber节点,由该Fiber节点向上层父级遍历,收集一条事件队列,再遍历该队列触发队列中每个Fiber对象对应的事件处理函数,正向遍历模拟冒泡,反向遍历模拟捕获,所以合成事件的触发时机是在原生事件之后的。

    Fiber对象对应的事件处理函数依旧是储存在PRops里的,收集只是从props里取出来,它并没有绑定到任何元素上。

    源码浅析

    以下源码仅为基础逻辑的浅析,旨在理清事件机制的触发流程,去掉了很多流程无关或复杂的代码。

    委托事件绑定

    这一步发生在调用了ReactDOM.render过程中,在创建fiberRoot的时候会在root节点的DOM元素上监听所有支持的事件。

    function createRootImpl(  container: Container,  tag: RootTag,  options: void | RootOptions,) {
          // ...  const rootContainerElement =        container.nodeTyPE === COMMENT_NODE ? container.parentNode : container;
          // 监听所有支持的事件  listenToAllSupportedEvents(rootContainerElement);
      // ...}
        

    listenToAllSupportedEvents

    在绑定事件时,会通过名为allNativeEvents的Set变量来获取对应的eventName,这个变量会在一个顶层函数进行收集,而nonDelegatedEvents是一个预先定义好的Set。

    export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
          allNativeEvents.foreach(domEventName =>
     {
        // 排除不需要委托的事件    if (!nonDelegatedEvents.has(domEventName)) {
              // 冒泡      listenToNativeEvent(        domEventName,        false,        ((rootContainerElement: any): Element),        null,      );
        }
            // 捕获    listenToNativeEvent(      domEventName,      true,      ((rootContainerElement: any): Element),      null,    );
      }
        );
    }
        

    listenToNativeEvent

    listenToNativeEvent函数在绑定事件之前会先将事件名在DOM元素中标记,判断为false时才会绑定。

    export function listenToNativeEvent(  domEventName: DOMEventName,  isCapturePhaseListener: boolean,  rootContainerElement: EventTarget,  targetElement: Element | null,  eventSystemFlags?: EventSystemFlags = 0,): void {
          let target = rootContainerElement;
        	// ...  // 在DOM元素上储存一个Set用来标识当前元素监听了那些事件  const listenerSet = getEventListenerSet(target);
          // 事件的标识key,字符串拼接处理了下  const listenerSetKey = getListenerSetKey(    domEventName,    isCapturePhaseListener,  );
      if (!listenerSet.has(listenerSetKey)) {
        // 标记为捕获    if (isCapturePhaseListener) {
              eventSystemFlags |= IS_CAPTURE_PHASE;
        }
            // 绑定事件    addTrappedEventListener(      target,      domEventName,      eventSystemFlags,      isCapturePhaseListener,    );
            // 添加到set    listenerSet.add(listenerSetKey);
      }
    }
        

    addTrappedEventListener

    addTrappedEventListener函数会通过事件名取得对应优先级的listener函数,在交由下层函数处理事件绑定。

    这个listener函数是一个闭包函数,函数内能访问targetContainer、domEventName、eventSystemFlags这三个变量。

    function addTrappedEventListener(  targetContainer: EventTarget,  domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,  isCapturePhaseListener: boolean,  isDeferredListenerForLegacyFBSupport?: boolean,) {
          // 根据优先级取得对应listener  let listener = createEventListenerWrapperWIThPriority(    targetContainer,    domEventName,    eventSystemFlags,  );
      if (isCapturePhaseListener) {
            addEventCaptureListener(targetContainer, domEventName, listener);
      }
     else {
            addEventBubbleListener(targetContainer, domEventName, listener);
      }
    }
        

    addEventCaptureListener函数和addEventBubbleListener函数内部就是调用原生的target.addEventListener来绑定事件了。

    这一步是循环一个存有事件名的Set,将每一个事件对应的处理函数绑定到root节点DOM元素上。

    不需要委托事件绑定

    不需要委托的事件其中也包括媒体元素的事件。

    export const nonDelegatedEvents: SetDOMEventName>
         = new Set([  'cancel',  'close',  'invalid',  'load',  'scroll',  'toggle',  ...mediaEventTypes,]);
        export const mediaEventTypes: ArrayDOMEventName>
         = [  'abort',  'canplay',  'canplaythrough',  'durationchange',  'emptied',  'encrypted',  'ended',  'error',  'loadeddata',  'loadedmetadata',  'loadstart',  'pause',  'play',  'playing',  'progress',  'ratechange',  'seeked',  'seeking',  'stalled',  'suspend',  'timeupdate',  'volumechange',  'waiting',];
        

    setInitialProperties

    setInitialProperties方法里会绑定不需要委托的直接到DOM元素本身,也会设置style和一些传入的DOM属性。

    export function setInitialProperties(  domElement: Element,  tag: string,  rawProps: Object,  rootContainerElement: Element | Document,): void {
          let props: Object;
      switch (tag) {
            // ...    case 'video':    case 'audio':      for (let i = 0;
         i  mediaEventTypes.length;
     i++) {
                listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
          }
              props = rawProps;
              break;
            default:      props = rawProps;
      }
          // 设置DOM属性,如style...  setInitialDOMProperties(    tag,    domElement,    rootContainerElement,    props,    isCustomcomponentTag,  );
    }
        

    switch里会根据不同的元素类型,绑定对应的事件,这里只留下了video元素和audio元素的处理,它们会遍历mediaEventTypes来将事件绑定在DOM元素本身上。

    listenToNonDelegatedEvent

    listenToNonDelegatedEvent方法逻辑和上一节的listenToNativeEvent方法基本一致。

    export function listenToNonDelegatedEvent(  domEventName: DOMEventName,  targetElement: Element,): void {
          const isCapturePhaseListener = false;
          const listenerSet = getEventListenerSet(targetElement);
          const listenerSetKey = getListenerSetKey(    domEventName,    isCapturePhaseListener,  );
      if (!listenerSet.has(listenerSetKey)) {
            addTrappedEventListener(      targetElement,      domEventName,      IS_NON_DELEGATED,      isCapturePhaseListener,    );
            listenerSet.add(listenerSetKey);
      }
    }
        

    值得注意的是,虽然事件处理绑定在DOM元素本身,但是绑定的事件处理函数不是代码中传入的函数,后续触发还是会去收集处理函数执行。

    事件处理函数

    事件处理函数指的是React中的默认处理函数,并不是代码里传入的函数。

    这个函数通过createEventListenerWrapperWithPriority方法创建,对应的步骤在上文的addTrappedEventListener中。

    createEventListenerWrapperWithPriority

    export function createEventListenerWrapperWithPriority(  targetContainer: EventTarget,  domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,): Function {
          // 从内置的Map中获取事件优先级  const eventPriority = getEventPriorityForPluginSystem(domEventName);
          let listenerWrapper;
      // 根据优先级不同返回不同的listener  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,  );
    }
        

    createEventListenerWrapperWithPriority函数里返回对应事件优先级的listener,这3个函数都接收4个参数。

    function fn(  domEventName,  eventSystemFlags,  container,  nativeEvent,) {
      //...}
        

    返回的时候bind了一下传入了3个参数,这样返回的函数为只接收nativeEvent的处理函数了,但是能访问前3个参数。

    dispatchDiscreteEvent方法和dispatchUserBlockingUpdate方法内部其实都调用的dispatchEvent方法。

    dispatchEvent

    这里删除了很多代码,只看触发事件的代码。

    export function dispatchEvent(  domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,  targetContainer: EventTarget,  nativeEvent: AnyNativeEvent,): void {
          // ...  // 触发事件  attemptToDispatchEvent(    domEventName,    eventSystemFlags,    targetContainer,    nativeEvent,  );
      // ...}
        

    attemptToDispatchEvent方法里依然会处理很多复杂逻辑,同时函数调用栈也有几层,我们就全部跳过,只看关键的触发函数。

    dispatchEventsForPlugins

    dispatchEventsForPlugins函数里会收集触发事件开始各层级的节点对应的处理函数,也就是我们实际传入JSX中的函数,并且执行它们。

    function dispatchEventsForPlugins(  domEventName: DOMEventName,  eventSystemFlags: EventSystemFlags,  nativeEvent: AnyNativeEvent,  targetInst: null | Fiber,  targetContainer: EventTarget,): void {
          const nativeEventTarget = getEventTarget(nativeEvent);
          const dispatchQueue: DispatchQueue = [];
          // 收集listener模拟冒泡  extractEvents(    dispatchQueue,    domEventName,    targetInst,    nativeEvent,    nativeEventTarget,    eventSystemFlags,    targetContainer,  );
          // 执行队列  proceSSDispatchQueue(dispatchQueue, eventSystemFlags);
    }
        

    extractEvents

    extractEvents函数里主要是针对不同类型的事件创建对应的合成事件,并且将各层级节点的listener收集起来,用来模拟冒泡或者捕获。

    这里的代码较长,删除了不少无关代码。

    function extractEvents(  dispatchQueue: DispatchQueue,  domEventName: DOMEventName,  targetInst: null | Fiber,  nativeEvent: AnyNativeEvent,  nativeEventTarget: null | EventTarget,  eventSystemFlags: EventSystemFlags,  targetContainer: EventTarget,): void {
          const reactName = topLevelEventStoreactnames.get(domEventName);
          let SyntheticEventCtor = SyntheticEvent;
          let reactEventType: string = domEventName;
    	// 根据不同的事件来创建不同的合成事件  switch (domEventName) {
            case 'keyPress':    case 'keydown':    case 'keyup':      SyntheticEventCtor = SyntheticKeyboardEvent;
              break;
            case 'click':    // ...    case 'mouseover':      SyntheticEventCtor = SyntheticMouseEvent;
              break;
            case 'Drag':    // ...    case 'drop':      SyntheticEventCtor = SyntheticdragEvent;
              break;
            // ...    default:      break;
      }
          // ...  // 收集各层级的listener  const listeners = accumulateSinglePhaseListeners(    targetInst,    reactName,    nativeEvent.type,    inCapturePhase,    accumulateTargetOnly,  );
          if (listeners.length >
     0) {
            // 创建合成事件    const event = new SyntheticEventCtor(      reactName,      reactEventType,      null,      nativeEvent,      nativeEventTarget,    );
        dispatchQueue.push({
    event, listeners}
        );
      }
    }
        

    accumulateSinglePhaseListeners

    accumulateSinglePhaseListeners函数里就是在向上层遍历来收集一个列表后面会用来模拟冒泡。

    export function accumulateSinglePhaseListeners(  targetFiber: Fiber | null,  reactName: string | null,  nativeEventType: string,  inCapturePhase: boolean,  accumulateTargetOnly: boolean,): ArrayDispatchListener>
     {
          const capturename = reactName !== null ? reactName + 'Capture' : null;
          const reactEventName = inCapturePhase ? captureName : reactName;
          const listeners: ArrayDispatchListener>
         = [];
          let instance = targetFiber;
          let lastHostComponent = null;
      // 通过触发事件的fiber节点向上层遍历收集dom和listener  while (instance !== null) {
        const {
    stateNode, tag}
         = instance;
            // 只有HostComponents有listener (i.e. div>
        )    if (tag === HostComponent &
        &
     stateNode !== null) {
              lastHostComponent = stateNode;
          if (reactEventName !== null) {
                // 从fiber节点上的props中获取传入的事件listener函数        const listener = getListener(instance, reactEventName);
            if (listener != null) {
              listeners.push({
                instance,            listener,            currentTarget: lastHostComponent,          }
        );
            }
          }
        }
        if (accumulateTargetOnly) {
              break;
        }
            // 继续向上    instance = instance.return;
      }
          return listeners;
    }
    

    最后的数据结构如下:

    dispatchQueue的数据结构为数组,类型为[{ event,listeners } ]。

    这个listeners则为一层一层收集到的数据,类型为[{ currentTarget, instance, listener } ]

    processDispatchQueue

    processDispatchQueue函数里会遍历dispatchQueue。

    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);
      }
    }
        

    dispatchQueue中的每一项在ProcessDispatchQueueItemsInOrder函数里遍历执行。

    processDispatchQueueItemsInOrder

    function processDispatchQueueItemsInOrder(  event: ReactSyntheticEvent,  dispatchListeners: ArrayDispatchListener>
    ,  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;
          }
              executeDispatch(event, listener, currentTarget);
              previousInstance = instance;
        }
      }
     else {
          // 冒泡    for (let i = 0;
         i  dispatchListeners.length;
     i++) {
          const {
    instance, currentTarget, listener}
         = dispatchListeners[i];
              if (instance !== previousInstance &
        &
     event.isPropagationStopped()) {
                return;
          }
              executeDispatch(event, listener, currentTarget);
              previousInstance = instance;
        }
      }
    }
        

    processDispatchQueueItemsInOrder函数里会根据判断来正向、反向的遍历来模拟冒泡和捕获。

    executeDispatch

    executeDispatch函数里会执行listener。

    function executeDispatch(  event: ReactSyntheticEvent,  listener: Function,  currentTarget: EventTarget,): void {
          const type = event.type || 'unknown-event';
          event.currentTarget = currentTarget;
          listener(event);
          event.currentTarget = null;
    }
        

    结语

    本文旨在理清事件机制的执行,按照函数执行栈简单的罗列了代码逻辑,如果不对照代码看是很难看明白的,原理在开篇就讲述了。

    React的事件机制隐晦而复杂,根据不同情况做了非常多的判断,并且还有优先级相关代码、合成事件,这里都没有一一讲解,原因当然是我还没看~

    平时用React也就写写简单的手机页面,以前老板还经常吐槽加载不够快,那也没啥办法,就对我的工作而言,有没有Cocurrent都是无关紧要的,这合成事件更复杂,完全就是不需要的,不过React的作者们脑洞还是牛皮,要是没看源码我肯定是想不到竟然模拟了一套事件机制。

    小思考

    • 为什么原生事件的stopPropagation可以阻止合成事件的传递?

    这些问题我放以前根本没想过,不过今天看了源码以后才想的。

    • 因为合成事件是在原生事件触发之后才开始收集并触发的,所以当原生事件调用stopPropagation阻止传递后,根本到不到root节点,触发不了React绑定的处理函数,自然合成事件也不会触发,所以原生事件不是阻止了合成事件的传递,而是阻止了React中绑定的事件函数的执行。
    div 原生onClick={
        (e)=>
    {
    e.stopPropagation()}
    }
        >
      div onClick={
        ()=>
    {
    console.LOG("合成事件")}
    }
        >
        合成事件/div>
        /div>
        

    比如这个例子,在原生onClick阻止传递后,控制台连“合成事件”这4个字都不会打出来了。

    以上就是React事件机制源码解析的详细内容,更多关于React事件机制源码的资料请关注其它相关文章!

    您可能感兴趣的文章:
    • React合成事件详解
    • React中事件绑定this指向三种方法的实现
    • React事件节流效果失效的原因及解决
    • 详细分析React 表单与事件
    • React学习之JSX与react事件实例分析
    • 通过实例学习React中事件节流防抖
    • React中阻止事件冒泡的问题详析
    • 详解Python的Twisted框架中reactor事件管理器的用法
    • react合成事件与原生事件的相关理解

    声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

    React事件

    若转载请注明出处: React事件机制源码解析
    本文地址: https://pptw.com/jishu/595168.html
    asp.net列出某文件夹下的所有文档,包括子目录下的档案 asp.net中显示1至20相同数字相乘的结果,若值比50小就不显示

    游客 回复需填写必要信息