react合成事件

react合成事件

概念

react合成事件是react模拟原生dom事件所有能力得一个事件对象。拥有与浏览器原生事件相同的接口。

本篇文章基于V17.0.3来研究react的合成事件

都知道react16是把事件绑定在document上,自己实现了一套事件机制,react17的区别是把事件绑定到根节点

起源

reactjsx解析成element的格式。带有事件的情况会解析成如下所示:

props解析携带onClick

{$$typeof: Symbol(react.element), type: 'div', key: null, ref: null, props: {},}
$$typeof: Symbol(react.element)
key: null
props: {className: 'border', children: {}, onClick: ƒ}
ref: null
type: "div"
_owner: null
_store: {validated: false}
_self: undefined
_source: {fileName: 'C:\\workspace\\study\\react-study\\hooks\\src\\index.js', lineNumber: 11, columnNumber: 3}
[[Prototype]]: Object

beginwork 阶段解析上述数据为fiber结构 memoizedPropspendingProps

react是什么时候将事件绑定到root上的?

在函数createRootImpl调用listenToAllSupportedEvents监听所有的支持的事件类型绑定事件类型

// 循环遍历所有的浏览器事件枚举。注册添加监听事件,监听root的事件,  
allNativeEvents.forEach(function (domEventName) {
      if (!nonDelegatedEvents.has(domEventName)) {
        listenToNativeEvent(domEventName, false, rootContainerElement, null);
      }

      listenToNativeEvent(domEventName, true, rootContainerElement, null);
    });

调用listenToNativeEvent下的addTrappedEventListener为每一种事件种类添加派发函数


function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
    // 创建派发函数
  var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the event will be
  // active and not passive.

  var isPassiveListener = undefined;

  if (passiveBrowserEventsSupported) {
    if (domEventName === 'touchstart' || domEventName === 'touchmove' || domEventName === 'wheel') {
      isPassiveListener = true;
    }
  }

  targetContainer =  targetContainer;
  var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we

// 添加root监听器
  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);
    }
  }
}

addTrappedEventListener做了两件事,一件是创建派发函数,一件是往root接点添加原生事件监听器

派发函数createEventListenerWrapperWithPriority

通过bind 传递domEventNameeventSystemFlagstargetContainer。注册事件时,targetContainerroot

function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
  var eventPriority = getEventPriorityForPluginSystem(domEventName);
  var 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);
}

添加监听函数addEventCaptureListener,为root添加上event监听事件。监听函数为dispatchEventdispatchDiscreteEventdispatchUserBlockingUpdate

function addEventCaptureListener(target, eventType, listener) {
  target.addEventListener(eventType, listener, true);
  return listener;
}

render开始阶段,对事件处理并添加到root上添加监听事件,通过root去捕获页面的事件

react是怎么触发事件的?

root上有事件监听,当我们点击事件时就触发了绑定在root上的handler,执行react的派发函数

调用顺序dispatchEvent->attemptToDispatchEvent->dispatchEventForPluginEventSystem->dispatchEventsForPlugins->extractEvents->processDispatchQueue->processDispatchQueueItemsInOrder->executeDispatch

attemptToDispatchEvent通过原生事件的event.taget|| event.srcElement获取到当前点击的dom元素

function attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
  // 获取当前点击的元素,便于节点自底向上查找
  var nativeEventTarget = getEventTarget(nativeEvent);
    // 找到fiber
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null) {
    var nearestMounted = getNearestMountedFiber(targetInst);

    if (nearestMounted === null) {
      // This tree has been unmounted already. Dispatch without a target.
      targetInst = null;
    } else {
      var tag = nearestMounted.tag;

      if (tag === SuspenseComponent) {
        var instance = getSuspenseInstanceFromFiber(nearestMounted);

        if (instance !== null) {
         
          return instance;
        } 


        targetInst = null;
      } else if (tag === HostRoot) {
        var root = nearestMounted.stateNode;

        if (root.hydrate) {
          
          return getContainerFromFiber(nearestMounted);
        }

        targetInst = null;
      } else if (nearestMounted !== targetInst) {
        targetInst = null;
      }
    }
  }

  dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer); // We're not blocked on anything.

  return null;
}

reactstateNode在渲染之初就有标记每个真实接点对应得fiber,上诉我们获取到了dom节点,可以通过这个标记找到该节点对应得fiber,生成时机在生成真实dom时。会往stateNode上添加一个唯一值由react+随机数生成。且随机数全局定义。

 var targetInst = targetNode[internalInstanceKey];

  if (targetInst) {
    // Don't return HostRoot or SuspenseComponent here.
    return targetInst;
  } 

dispatchEventsForPlugins根据fiber顺序执行extractEventsprocessDispatchQueue

function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
    // 获取原生dom
  var nativeEventTarget = getEventTarget(nativeEvent);
  var dispatchQueue = [];
  extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents$5根据domEvent查询react事件名。将fiber上的事件比如:onClick绑定的函数推入dispatchQueue,调用accumulateSinglePhaseListeners->getListener,根据fiber的return自底向上查找对应事件名,getListener根据propsreact事件名读取,dispatchQueue结构是数组对象,对象包含eventlistener,用于后续实际触发执行

// 自定义事件类型 
switch (domEventName) {
    case 'pointerup':
      SyntheticEventCtor = SyntheticPointerEvent;
      break;
  }
  // 
  var _listeners = accumulateSinglePhaseListeners(targetInst, reactName, nativeEvent.type, inCapturePhase, accumulateTargetOnly);

    if (_listeners.length > 0) {
      // Intentionally create event lazily.
      var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget);

      dispatchQueue.push({
        event: _event,
        listeners: _listeners
      });
    }

function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly) {
  var captureName = reactName !== null ? reactName + 'Capture' : null;
  var reactEventName = inCapturePhase ? captureName : reactName;
  var listeners = [];
  var instance = targetFiber;
  var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path.
// 自顶向下查找
  while (instance !== null) {
    var _instance2 = instance,
        stateNode = _instance2.stateNode,
        tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. <div>)

    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode; // createEventHandle listeners


      if (reactEventName !== null) {
        var listener = getListener(instance, reactEventName);
        console.log('qy:-----listener',listener);
        if (listener != null) {
          listeners.push(createDispatchListener(instance, listener, lastHostComponent));
        }
      }
    } // If we are only accumulating events for the target, then we don't
    // continue to propagate through the React fiber tree to find other
    // listeners.


    if (accumulateTargetOnly) {
      break;
    }

    instance = instance.return;
  }

  return listeners;
} 
// 自顶向下查找
function getListener(inst, registrationName) {
  var stateNode = inst.stateNode;

  if (stateNode === null) {
    // Work in progress (ex: onload events in incremental mode).
    return null;
  }

  var props = getFiberCurrentPropsFromNode(stateNode);

  if (props === null) {
    // Work in progress.
    return null;
  }
	
  var listener = props[registrationName];

  if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
    return null;
  }

  if (!(!listener || typeof listener === 'function')) {
    {
      throw Error( "Expected `" + registrationName + "` listener to be a function, instead got a value of `" + typeof listener + "` type." );
    }
  }

  return listener;
}

执行顺序processDispatchQueue->processDispatchQueueItemsInOrder->executeDispatch

processDispatchQueue遍历dispatchQueueprocessDispatchQueueItemsInOrder遍历某类事件的所有listenersexecuteDispatch,执行函数

 for (var i = 0; i < dispatchQueue.length; i++) {
    var _dispatchQueue$i = dispatchQueue[i],
        event = _dispatchQueue$i.event,
        listeners = _dispatchQueue$i.listeners;
    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.
  for (var i = dispatchListeners.length - 1; i >= 0; i--) {
      var _dispatchListeners$i = dispatchListeners[i],
          instance = _dispatchListeners$i.instance,
          currentTarget = _dispatchListeners$i.currentTarget,
          listener = _dispatchListeners$i.listener;

      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }

      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
react为什么给click事件的hander绑定上空函数?

从浏览器的事件监听可以看到react将这个handler处理成空函数,源码内,为什么要处理?从源码内部可以知道是为了解决移动 Safari 无法正确触发气泡点击事件

 if (typeof rawProps.onClick === 'function') {
        // TODO: This cast may not be sound for SVG, MathML or custom elements.
        trapClickOnNonInteractiveElement(domElement);
      }
function trapClickOnNonInteractiveElement(node) {
  // Mobile Safari does not fire properly bubble click events on
  node.onclick = noop;
}
react为什么要自己实现一个合成事件?
  • 节省内存消耗。

    浏览器在处理事件的挂载和销毁会消耗内存,而react通过root挂载监听,委托分发,节省了内存消耗。

  • 跨端兼容,实现全浏览器得一致性,统一规范

react如何阻止冒泡事件?
  stopPropagation: function () {
      var event = this.nativeEvent;

      if (!event) {
        return;
      }

      if (event.stopPropagation) {
        event.stopPropagation(); 
      } else if (typeof event.cancelBubble !== 'unknown') {
        event.cancelBubble = true;
      }

      this.isPropagationStopped = functionThatReturnsTrue;
    },
        

借助原生event.stopPropagation去阻止。并且在executeDispatch中检测到isPropagationStoppedtrue时,不执行listener,达到阻止冒泡

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值