合成事件和原生事件的区别

原生事件: 在 componentDidMount 生命周期里边进行 addEventListener 绑定的事件

合成事件: 通过 JSX 方式绑定的事件,比如 onClick={() => this.handle()}

因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理

浏览器事件处理机制

捕获阶段-目标元素阶段-冒泡阶段。

Question: 此时对于合成事件进行阻止,原生事件会执行吗?答案是: 会!

因为原生事件先于合成事件执行 (个人理解: 注册的原生事件已经执行,而合成事件处于目标阶段,它阻止的冒泡只是阻止合成的事件冒泡,但是原生事件在捕获阶段就已经执行了)

合成事件的特点

React 上注册的事件最终会绑定在 document 这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)

React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation() 无效的原因。

React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback

React 有一套自己的合成事件 SyntheticEvent,不是原生的。

React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能

ReactEventListener:负责事件的注册。 ReactEventEmitter:负责事件的分发。 EventPluginHub:负责事件的存储及分发。 Plugin:根据不同的事件类型构造不同的合成事件。

export function listenTo(
  registrationName: string,
  mountAt: Document | Element | Node
): void {
  const listeningSet = getListeningSetForElement(mountAt)
  const dependencies = registrationNameDependencies[registrationName]

  for (let i = 0; i < dependencies.length; i++) {
    const dependency = dependencies[i]
    // 调用该方法进行注册
    listenToTopLevel(dependency, mountAt, listeningSet)
  }
}

事件存储

function enqueuePutListener(inst, registrationName, listener, transaction) {
  //...
  // 注册事件,将事件注册到document上
  listenTo(registrationName, doc);
  // 存储事件,放入事务队列中
  transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener,
  });
}

在 document 的根节点上注册一个事件监听器。 当一个事件被触发,浏览器会告诉我们目标 dom 节点,

每一个 react 组件都会使用唯一的 id 来编码层级 我们可以通过简单的字符串操作来获取所有父级的 id。 通过把注册地事件监听器放在一个 hashMap 中,我们发现这样做的性能远比把它们关联到虚拟 DOM 要好

虚拟 DOM 上进行分发

// dispatchEvent('click', 'a.b.c', event)
clickCaptureListeners["a"](event);
clickCaptureListeners["a.b"](event);
clickCaptureListeners["a.b.c"](event);
clickBubbleListeners["a.b.c"](event);
clickBubbleListeners["a.b"](event);
clickBubbleListeners["a"](event);

浏览器会为每一个事件和事件监听器创建一个事件对象。 这个事件对象有一个很不错的属性就是你可以维护每一个事件对象的引用

最后更新于

这有帮助吗?