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

news2025/1/20 6:01:14

这一章我想跟大家探讨的是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 创建。例如,<div /> 会被 React 渲染为 DOM 节点,<MyComponent /> 会被 React 渲染为自定义组件,无论是 <div /> 还是 <MyComponent /> 均为 React 元素。
  • 数组或 fragments。 使得 render 方法可以返回多个元素。
  • Portals。可以渲染子节点到不同的 DOM 子树中。
  • 字符串或数值类型。它们在 DOM 中会被渲染为文本节点。
  • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中 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);

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

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

接下来看看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,
  );
}

在这里我们关注一下获取优先级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();

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

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

// packages/react-dom/src/events/DOMEventProperties.js
export function registerSimpleEvents() {
  // 处理离散事件优先级
  registerSimplePluginEventsAndSetTheirPriorities(
    discreteEventPairsForSimpleEventPlugin,
    DiscreteEvent,
  );
  // 处理用户阻塞事件优先级
  registerSimplePluginEventsAndSetTheirPriorities(
    userBlockingPairsForSimpleEventPlugin,
    UserBlockingEvent,
  );
  // 处理默认事件优先级
  registerSimplePluginEventsAndSetTheirPriorities(
    continuousPairsForSimpleEventPlugin,
    ContinuousEvent,
  );
  // 处理其他事件优先级
  setEventPriorities(otherDiscreteEvents, DiscreteEvent);
}

上述代码中可以看到有非常多的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',
];

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

registerTwoPhaseEvent

export function registerTwoPhaseEvent(
  registrationName: string, // 注册事件reactName
  dependencies: Array<DOMEventName>, // 依赖
): void {
  registerDirectEvent(registrationName, dependencies);
  registerDirectEvent(registrationName + 'Capture', dependencies);
}

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

前面说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 {
    ...
  }
}

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

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

在这里插入图片描述

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/84656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Python多人游戏项目实战》第一节 简单的方块移动

目录 1.1 设置游戏窗口 1.2 绘制一个方块 1.3 编写服务端代码 1.4 完善客户端代码 1.5 完整代码下载地址 在本节&#xff0c;我们将通过一个简单的方块移动程序进入多人联机游戏的大门。每个玩家打开游戏窗口后都可以控制一个方块&#xff0c;当某个玩移动方块后&#xff…

面试八股-Java并发

1.线程 和进程区别&#xff1a; 进程&#xff1a;独立地址空间 代码、数据、堆栈 资源分配基本单位线程&#xff1a;共享地址空间 线程ID、指令指针、寄存器集合和堆栈 调度分派基本单位 1.1.使用 实现Runable接口&#xff0c;run方法为实际逻辑实现Callable接口&#xff0…

离散数学与组合数学-数理逻辑-02谓词演算及其形式系统

文章目录第二章 谓词演算及其形式系统2.1 个体谓词和量词2.1.1 个体谓词演算永真式谓词公式的前束范式一阶谓词演算形式系统谓词逻辑的等值演算与推理第二章 谓词演算及其形式系统 2.1 个体谓词和量词 2.1.1 个体 个体常元(constants):确定的个体用a,b,ca,b,ca,b,c等小写字母…

羟基聚乙二醇叠氮 HO-PEG-N3/Azide的结构式

羟基聚乙二醇叠氮(HO-PEG-N3)是异双功能PEG衍生物之一。叠氮化物在铜离子催化的水溶液中与炔基有效反应。炔烃和叠氮化物之间的1,3-偶极环加成反应是一种高产率的点击化学反应&#xff0c;可实现两个相应分子的高效结合。叠氮化物也可以与应变促进的环辛炔反应&#xff0c;不需…

Pr:使用作品

利用作品 Production&#xff0c;可将大型复杂工作流拆分为多个可管理的 Pr 项目&#xff0c;并可在作品内跨项目、跨平台&#xff08;macOS 和 Windows&#xff09;引用媒体&#xff0c;无论这些资源是在本地存储或是共享的网络存储&#xff0c;作品可让一切保持同步。作品&am…

真希望你也明白runtime.Map和sync.Map

Map 官方介绍 One of the most useful data structures in computer science is the hash table. Many hash table implementations exist with varying properties, but in general they offer fast lookups, adds, and deletes. Go provides a built-in map type that imple…

Grafana监控大屏配置参数介绍(二)

Grafana 系列文章&#xff0c;版本&#xff1a;OOS v9.3.1 Grafana 的介绍和安装Grafana监控大屏配置参数介绍&#xff08;一&#xff09;Grafana监控大屏配置参数介绍&#xff08;二&#xff09; 上一篇文章已经介绍了图表可视化配置部分的 Panel options、Tooltip、Legend 3类…

音视频行业大势如何,优势在哪?

电信行业的变革&#xff1a; 从1G语音、2G短信、3G图片语音、4G视频到5G未来可期的新时代&#xff0c;见证了音视频行业的磅礴发展。 技术更新慢且门槛高 技术更新慢&#xff0c;技术门槛高&#xff0c;大部分技术沿用至今&#xff0c;依然保持生命力&#xff0c;技术人员成型…

http协议和websocket协议

http协议 HTTP 即超文本传输协议&#xff0c;是一种获取网络资源 (例如图像、HTML 文档) 的应用层协议&#xff0c;它是互联网数据通信的基础&#xff0c;由请求和响应构成。通常&#xff0c;首先客户端会发送 HTTP 请求(在请求报文中会指定资源的 URL)&#xff0c;然后用传输…

DocArray 0.20.0 发布!新增 Milvus 后端支持,更好地嵌套数据搜索,新增 RGB-D 格式的 3D 模型表示...

DocArray 是一个用于处理、传输和存储多模态数据的 Python 工具包。DocArray 提供便捷的多模态数据处理功能&#xff0c;具备基于 Protobuf 提供高性能的网络传输性能&#xff0c;同时也为多种向量存储方案提供统一的 API 接口。GitHub&#xff1a;github.com/docarray/docarra…

AU如何为你的人声增加空旷感?

你知道怎么使用AU给你的声音添加延迟效果&#xff0c;让你的声音具有空旷感和弱回声的效果。在这里我们可以使用插件达到这个目的。 在使用模拟延迟插件之前呢&#xff0c;我们可以去创建一个立体声总音轨&#xff0c;创建方式如图&#xff0c;跟着序号走&#xff0c;我们就可以…

CSS -- 06. CSS高阶技巧总结

文章目录CSS高阶技巧1 精灵图(sprites)1.1 为什么使用精灵图1.2 精灵图的使用2 字体图标2.1 字体图标的产生2.2 字体图标的优点2.3 字体图标的下载2.4 字体图标的引入2.5 字体图标的追加3 CSS三角形4 CSS用户界面样式4.1 鼠标样式 cursor4.2 表单的轮廓线4.3 防止拖拽文本域 re…

JAVA毕业设计——基于Springboot+vue的心理咨询管理系统(源代码+数据库)

github代码地址 https://github.com/ynwynw/psychlolgyhealth-public 毕业设计所有选题地址 https://github.com/ynwynw/allProject 基于Springbootvue的心理咨询管理系统(源代码数据库) 一、系统介绍 本项目分为管理员与普通用户两种角色 管理员角色包含以下功能&#xff…

[附源码]Python计算机毕业设计SSM基于Web美食网站设计(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Kotlin标准库函数

Kotlin标准库中包含了几个函数&#xff0c;它们的目的就是可以在对象的上下文中执行代码块。当我们调用该Lambda表达式时&#xff0c;它会形成一个临时的作用域。在该范围内&#xff0c;可以访问不带名称的对象&#xff0c;此类函数称为作用域函数。包括: apply函数let函数run…

Java开发才不到3年,来面试开口要25K,面完连10K都不想给

前言 我的好朋友兼大学同学老左家庭经济情况不错&#xff0c;毕业之后没两年自己存了点钱加上家里的支持&#xff0c;自己在杭州开了一家网络公司。由于公司不是很大所以公司大部分的开发人员都是自己面试的&#xff0c;近期公司发展的不错&#xff0c;打算扩招也面试了不少人。…

[l论文解析]Classifier-Free Diffusion Guidance

paper link&#xff1a;https://openreview.net/pdf?idqw8AKxfYbI 文章目录OverviewWhat problem is addressed in the paper?What is the key to the solution?What is the main contribution?Potential fundamental flaws; how this work can be improved?Content关于 c…

Java 字符串 split 的一个反直觉陷阱

最近生产环境遇到一个奇怪的数组下标越界报错&#xff0c;如下图代码所示&#xff0c;我们可以肯定的是 fieldName 变量不为空&#xff08;不是空字符串&#xff0c;也不是 null&#xff09;&#xff0c;但是代码执行到读取 names[0] 变量的时候&#xff0c;抛出了一个 数组下标…

5G无线技术基础自学系列 | 抗衰落技术

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 无线信道是随机时变信道&#xff0c;信…

【云计算与大数据技术】文件存储格式行式、列式、GFS、HDFS的讲解(图文解释 超详细)

一、分布式文件系统 文件系统最后都需要以一定的格式存储数据文件&#xff0c;常见的文件存储布局有行式存储、列式存储以及混合式存储三种&#xff0c;不同的类别各有其优缺点和适用的场景&#xff0c;在目前的大数据分析系统中&#xff0c;列式存储和混合式存储方案因其特殊…