Skip to content

事件的绑定

对于我们组件中对于某一个元素绑定一个事件的时候,这是怎么注册为React的合成事件的?

FiberNode事件的绑定是发生在completeWork阶段,在节点构建DOM元素的过程中 其经历了一下几个过程

  • createInstance()
  • appendAllChildren(instance, workInProgress, false, false);
  • finalizeInitialChildren()

finalizeInitialChildren()的过程主要就是处理DOM元素的属性的过程,在处理属性的过程中如遇到事件类型的属性,就会触发事件的绑定流程。

js
/**
 * 减Props的内容更新到 DOM元素 上
 *    此处主要处理以下:
 *   - style 属性
 *   - dangerouslySetInnerHTML
 *   - children属性
 *   - autoFocus
 *   - 其他 初始化注册的 事件 onClick  -- 通过 ensureListeningTo 绑定事件
 * @param {*} tag
 * @param {*} domElement
 * @param {*} rootContainerElement
 * @param {*} nextProps
 * @param {*} isCustomComponentTag
 */
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
  
    if (registrationNameDependencies.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        // 事件的回调得是函数类型的
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        // enableEagerRootListeners 默认为true,解决createPotal创建的container的问题 
        if (!enableEagerRootListeners) {
          ensureListeningTo(rootContainerElement, propKey, domElement);
        } else if (propKey === 'onScroll') {
          listenToNonDelegatedEvent('scroll', domElement);
        }
      }
    }
  }
}

从源码中可以得出,在处理FiberNode的DOM元素的属性时,其没有处理事件类型的属性(onClick, 注意这里处理 onScroll事件),那么onClick是怎么注册到 button元素上,怎么触发的呐?

前置知识

这就涉及到浏览签的事件体系

image

事件可以分为捕获与冒泡两个阶段,其事件的起点和终点都是window对象,目标对象都是事件触发的元素本身(target) 。那么是否就可以通过事件委托机制,在一个根元素上注册了所有的事件,然后通过 e.target去判断触发事件的目标对象。

React就是模拟的浏览器事件流的机制,不在元素节点上去注册事件,而是通过将所有的事件全部都注册到React16的document对象|React17的root对象上,然后通过 e.target寻找目标DOM,然后转换成目标的FiberNode对象,然以通过不同的 任务优先级去执行事件回调方法。这就是React的事件池的概念

通过上述completeWork阶段和React事件池概念的讲解,我们再回到事件系统的初始化流程去,这时候我们就会发现,在React初始化的时候,其不是初始化事件机制,其实也是绑定了所有的事件。

事件初始化及绑定流程

其核心方法是

js
// 根据事件的任务级别去构建不同的事件监听函数 
let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );

下面我们细细看这个方法

createEventListenerWrapperWithPriority()

React的一大特点就是任务调度,对于事件体系其触发回调也是通过任务调度的方式进行的。

在事件体系中,其将所有的任务分为3个任务等级()

js
export const DiscreteEvent: EventPriority = 0;
export const UserBlockingEvent: EventPriority = 1;
export const ContinuousEvent: EventPriority = 2;
  1. DiscreteEvent : 最高优先级的事件任务,比如我们大部分的事件 click , input , keyDown

    dispatchDiscreteEvent()进行任务调度

  2. UserBlockingEvent: 优先级适中, 包括drag, scroll等事件

    dispatchUserBlockingUpdate()进行任务调度

  3. ContinuousEvent: 优先级最低 ,包括animation, load等事件

    dispatchEvent()进行任务调度

源码在:

js

/**
 * 根据任务优先级进行事件的注册
 */
export function registerSimpleEvents() {
  // 最高优先级事件任务 - 0
  registerSimplePluginEventsAndSetTheirPriorities(
    discreteEventPairsForSimpleEventPlugin, // 注册的最高优先级任务列表
    DiscreteEvent, // 任务的优先级 - 0
  );
  // 次优先级事件任务  - 1
  registerSimplePluginEventsAndSetTheirPriorities(
    userBlockingPairsForSimpleEventPlugin,
    UserBlockingEvent,
  );
  // 最低优先级事件任务 - 2
  registerSimplePluginEventsAndSetTheirPriorities(
    continuousPairsForSimpleEventPlugin,
    ContinuousEvent,
  );
  // 将一些不能通过事件代理的方式处理的事件,单独处理
  // selectionchange
  setEventPriorities(otherDiscreteEvents, DiscreteEvent);
}

下面我们具体看一下三种不同优先级的任务具体哪里不同

js

/**
 * 最高优先级的事件任务 调度的事件处理
 * @param {*} domEventName 
 * @param {*} eventSystemFlags 
 * @param {*} container 
 * @param {*} nativeEvent 
 */
function dispatchDiscreteEvent(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
  // discreteUpdates的方式触发dispatchEvent
  discreteUpdates(
    dispatchEvent,
    domEventName,
    eventSystemFlags,
    container,
    nativeEvent,
  );
}

function dispatchUserBlockingUpdate(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
   // runWithPriority 触发dispatchEvent的回调
    runWithPriority(
      UserBlockingPriority,
      dispatchEvent.bind(
        null,
        domEventName,
        eventSystemFlags,
        container,
        nativeEvent,
      ),
    );
}
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
}

可见其任务的回调都是通过 dispatchEvent 进行,

  • 最高优先的通过dispatchDiscreteEvent()创建了一个 isInsideEventHandler 执行环境
  • 次优先级的通过 runWithPriority 去调用Scheduler任务调度,优先级为 1
  • 最低的 直接触发 dispatchEvent

dispatchDiscreteEvent()

栗子

js
      const Child = () => {
        const [visible, setVisible] = React.useState(true)
        const handleClickBtn = () => {
          setVisible(!visible)
        }
        return (
          <div>
            <button onClick={handleClickBtn}>切换</button>
          </div>
        )
      }
      const App = props => {
        const handleClickBtn = () => {
          console.log("App")
        }
        return (
          <div onClick={handleClickBtn}>
            <Child />
          </div>
        )
      }

其结果如下

image

image