likes
comments
collection
share

React之事件机制前言 React v18.3.1 React会所有支持的事件进行事件代理。事件触发时会由触发事件的f

作者站长头像
站长
· 阅读数 20

前言

React v18.3.1

本来只想整理listenToAllSupportedEvents()的,但单独整理listenToAllSupportedEvents()给人一种电影放了一半你才落座的感觉。干脆整理React的事件机制,直接贯通🌟~

React的事件机制是什么?

React会所有支持的事件进行#事件代理。事件触发时会由触发事件的fiberNode向上收集捕获->冒泡(#事件流)事件、创建合成事件对象。收集完成后逐一执行收集的事件。

简单来说,React事件机制就是事件代理、事件收集然后同类事件统一执行的机制。

为什么要自己实现一套事件机制?

  • 拥有onClick这样处理函数的fiberNode生成时,其DOM节点可能还未创建,所以onClick无法绑定到真实的DOM节点
  • 进行事件代理,不需监听每个元素的事件,减少了事件监听器的创建,节省内存
  • 使用合成事件对象磨平了浏览器差异,比如:取消冒泡nativeEvent.stopPropagation()nativeEvent.cancelBubble = true,在React只需使用syntheticEvent.stopPropagation()

React事件机制的实现细节

1. React事件分类

这里的事件分类是指对React事件进行分类,React事件会依赖一个或多个原生事件。对于使用者来说无需过多关心原生事件,直接使用React事件即可,比如:对于<input type="text" />、<input type="radio" />、<input type="checkbox" />统一使用onChange收集即可。

React根据React事件依赖原生事件的数量以及它们对应的操作将事件分为了5类,并通过各自的插件注册:

  • 输入前事件(BeforeInputEventPlugin):onBeforeInput、onCompositionEnd...
  • 表单变化事件(ChangeEventPlugin):onChange
  • 鼠标移入移除事件(EnterLeaveEventPlugin):onMouseEnter、onPointerEnter...
  • 选择事件(SelectEventPlugin):onSelect
  • 简单事件(SimpleEventPlugin):只依赖一个原生事件的React事件,如onClick、onKeyPress...

2. 收集支持代理、不支持代理的原生事件

由于React需要对所有事件进行代理,所以需要知道所有支持的原生事件。

在注册React事件时会将其依赖的原生事件添加到allNativeEvents集合中,并通过registrationNameDependencies对象维护React事件与其依赖的原生事件的关系。

对于不支持代理的事件会保存在nonDelegatedEvents集合中。在#commitWork阶段(留待源码学习完毕后归纳)给相应DOM元素添加属性时,监听nonDelegatedEvents。

// 注册React事件
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();

// 所有原生事件
const allNativeEvents = new Set<DOMEventName>([
    'click',
    'dbclick',
    'focus',
    ....
]);

const registrationNameDependencies = {
    onClick: ['click'],
    onChange: [
        'change',
        'click',
        'focusin',
        'focusout',
        'input',
        'keydown',
        'keyup',
        'selectionchange'
    ],
    ...
}

// 原生媒体事件
const mediaEventTypes: Array<DOMEventName> = [
  'pause',
  'play',
  'progress',
  ...
];

// 不支持冒泡的原生事件
const nonDelegatedEvents = new Set<DOMEventName>([cancel',
    'close',
    'invalid',
    'load',
    'scroll',
    'toggle',
    ...mediaEventTypes
]);

3. 事件代理

listenToAllSupportedEvents

  1. 在rootContainerElement上监听所有原生事件(不包括selectionchange):对于支持冒泡的事件会注册捕获、冒泡阶段的事件;对于不支持冒泡的事件只会注册捕获阶段的事件
  2. 获取document并监听selectionchange事件,在document上添加表示已进行事件委托的属性:listeningMarker

不管是rootContainerElement还是document,进行过事件代理后都会添加listeningMarker属性,表示已进行了事件代理,不会重复代理。

function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
    if (!(rootContainerElement: any)[listeningMarker]) {
        (rootContainerElement: any)[listeningMarker] = true;
        allNativeEvents.forEach(domEventName => {
            if (domEventName !== 'selectionchange') {
                if (!nonDelegatedEvents.has(domEventName)) {
                    // 注册冒泡阶段事件
                    listenToNativeEvent(domEventName, false, rootContainerElement);
                }
                // 注册捕获阶段事件
                listenToNativeEvent(domEventName, true, rootContainerElement);
            }
        });

        // 获取document
        const ownerDocument =
            (rootContainerElement: any).nodeType === DOCUMENT_NODE
                ? rootContainerElement
                : (rootContainerElement: any).ownerDocument;
        if (ownerDocument !== null) {
            if (!(ownerDocument: any)[listeningMarker]) {
                // 在document上监听selectchange事件
                (ownerDocument: any)[listeningMarker] = true;
                listenToNativeEvent('selectionchange', false, ownerDocument);
            }
        }
    }
}

listenToNativeEvent

listenToNativeEvent()用于监听原生事件。它有3个参数:

  • domEventName: 原生事件名
  • isCapturePhaseListener: 是否捕获阶段
  • target: 绑定事件的目标元素

listenToNativeEvent()内部会调用其他方法(因为本身对源码还不是很了解,所以这里不继续拓展了。等源码学习完毕再回头整理),这里简单概括下:

  1. 根据事件名称获取事件优先级
  2. 根据事件优先级获取对应的dispatchEvent()
  3. 将dispatchEvent()作为事件监听器绑定到目标元素上

4. 事件触发

以SimpleEventPlugin为例,当代理的事件监听器((dispatchEvent())触发时,React会做如下3件事:

  1. #从触发事件的fiberNode开始向上收集沿途的捕获、冒泡事件
  2. #创建合成事件,与收集到的事件一起加入事件队列中()
  3. #按捕获->冒泡的顺序执行事件队列中的事件

边看边写给看晕了,来张图总结一下

React之事件机制前言 React v18.3.1 React会所有支持的事件进行事件代理。事件触发时会由触发事件的f

可能有理解错误的地方,等坚持学下去、对源码的理解更深后再做整理。

待办

  • 整理事件委托
  • 整理事件流
  • 整理commitWork阶段
  • 整理accumulateSinglePhaseListeners()
  • 整理extractEvents()
  • 整理processDispatchQueue()
转载自:https://juejin.cn/post/7405778015189368869
评论
请登录