React事件机制面试题收集

112 阅读6分钟

题目整理

基础篇

  1. React 的合成事件系统是什么?它和原生事件有何不同?
  2. 为什么不能在 setTimeout 或 Promise 中访问 event.target.value
  3. onClick 和 onClickCapture 有什么区别?它们的执行顺序是怎样的?
  4. 如果父子组件都绑定了 onClick,React 是否会创建多个事件对象?
  5. React 如何阻止事件冒泡?
  6. 事件对象中的 e.target 和 e.currentTarget 有什么区别?

进阶篇

  1. React 的事件委托机制是如何实现的?
  2. React 的事件池和SyntheticEvent是什么?它是如何提升性能的?
  3. React 中的 SyntheticEvent 是什么?如何创建和复用?
  4. React 模拟事件是否依赖 DOM 的事件传播机制?
  5. React 中的事件传播路径是如何构建的?
  6. React 的事件系统是如何支持批量更新的?和 batchedUpdates 有什么关系?
  7. React 是如何确保组件卸载时移除事件监听器的?这个过程发生在哪个阶段?

困难篇

  1. React 在 commit 阶段是如何清理事件相关引用的?
  2. 事件队列是否真实存在?React 是如何组织事件回调的执行顺序的?
  3. React 中的 getPooled() 操作会在每次事件触发时调用吗?
  4. e.persist() 被不推荐使用的原因是什么?
  5. React 是如何判断一个事件是否应该被批处理的?
  6. React 的合成事件系统是如何与调度器(Scheduler)配合工作的?
  7. 如果你要为 React 添加一个新的事件类型(如 onSwipe),你会怎么做?需要修改哪些模块?
  8. React 的事件系统是否兼容服务端渲染(SSR)?如果是,它是如何处理事件绑定的?
  9. React Native 中的事件系统与 Web 端有何不同?
  10. React 的事件插件系统是什么?它是如何扩展事件类型和自定义事件的?

题目解答 - 基础篇

React 的合成事件系统是什么?它和原生事件有何不同?

 React 的合成事件系统是 React 对浏览器原生事件的封装,核心是事件委托 + 事件池。它通过事件委托机制将所有事件监听统一绑定在根 DOM 容器(如 #root),然后根据 Fiber 树结构模拟出事件传播路径,在捕获和冒泡阶段依次执行组件绑定的事件回调,同时使用事件对象池(Pooling)复用 SyntheticEvent 实例以提升性能,并支持批量更新机制优化状态变更的渲染效率;与原生事件不同,合成事件不是直接绑定在目标元素上,而是由 React 统一管理,具有跨平台一致性

为什么不能在 setTimeout 或 Promise 中访问 event.target.value

 因为在 setTimeout 或 Promise 中,React 的合成事件对象(event)会被回收并清空属性(由于事件池机制),导致访问 event.target.value 时值已不可用

onClick 和 onClickCapture 有什么区别?它们的执行顺序是怎样的?

onClick 在冒泡阶段执行,onClickCapture 在捕获阶段执行,这里的冒泡和捕获都是模拟事件。执行顺序:父 onClickCapture、子 onClickCapture、子 onClick、父 onClick

如果父子组件都绑定了 onClick,React 是否会创建多个事件对象?

 不会。React 不会创建多个事件对象,而是复用同一个合成事件对象,只是在不同组件回调中动态更新其属性(如 currentTarget

React 如何阻止事件冒泡?

 e.stopPropagation()

题目解答 - 进阶篇

React 的事件委托机制是如何实现的?

 React 的事件委托机制是通过将所有事件监听器统一绑定到根 DOM 容器(如 #root)来实现的,而不是直接在每个 DOM 元素上绑定原生事件;当用户触发一个事件时,事件首先冒泡到根节点,再由 React 根据 Fiber 树结构模拟出捕获和冒泡阶段,手动遍历路径并调用对应组件的事件处理函数

React 的事件池和SyntheticEvent是什么?它是如何提升性能的?

SyntheticEvent 是 React 提供的一个跨浏览器的事件包装对象,它屏蔽了不同浏览器之间的实现差异,为开发者提供统一的事件接口。当事件被触发时,React 并不会每次都创建新的 SyntheticEvent 对象,而是从一个叫做“事件池”的地方取出一个已存在的对象并重置其属性进行复用,事件处理完成后该对象会被回收回池中,等待下次使用。

 这种事件池机制有效减少了频繁创建和销毁事件对象带来的性能开销,降低了内存分配和垃圾回收的压力。此外,React 还通过事件委托的方式进一步优化性能,将事件监听统一绑定在顶层(如 document),再根据冒泡机制分发事件,而不是为每个 DOM 元素单独绑定监听器。

React 中的 SyntheticEvent 是什么?如何创建和复用?

 SyntheticEvent 是 React 对浏览器原生事件进行的一层统一封装。当用户触发一个事件(如点击)时,React 会通过 getPooled 方法从内部维护的事件池中取出一个 SyntheticEvent 实例,如果事件池中没有可用对象,则新建一个;接着,React 会用当前的原生事件和目标 Fiber 节点信息初始化这个合成事件对象,并将其传入相应的事件处理函数中执行;在事件回调执行完成后,React 会调用 release 方法将该事件对象清空并放回事件池中,以便下次复用。同时,在事件传播的不同阶段,React 会动态更新 SyntheticEvent 对象的关键属性(如 currentTarget 和 eventPhase),复用 SyntheticEvent。

React 模拟事件是否依赖 DOM 的事件传播机制?

 部分依赖。当事件发生时,利用浏览器原生事件的传播机制找到对应的 React 组件

React 中的事件传播路径是如何构建的?

 React 在事件触发时,会从目标 Fiber 节点开始向上遍历 Fiber 树,直到根节点,构建出一条完整的路径,该路径用于模拟事件的传播过程,在捕获阶段 React 会从根节点向目标节点方向依次遍历路径上的每个 Fiber 节点,检查其 memoizedProps 中是否存在 *Capture 类型的事件回调并执行,在冒泡阶段则从目标节点向根节点方向再次遍历路径上的每个节点,查找并执行非 *Capture 类型的事件回调,从而实现对事件传播顺序的精确控制,整个过程不依赖原生 DOM 的事件机制,完全由 React 内部调度和管理

题目解答 - 困难篇

React 中的 getPooled() 操作会在每次事件触发时调用吗?

 会,调用一次 getPooled() 获取一个 SyntheticEvent

e.persist() 被不推荐使用的原因是什么?

 主要是 Concurrent 模式下异步可中断渲染使持久化事件对象变得不可靠。在 React 的 Concurrent 模式下,渲染过程是异步可中断的,这意味着事件触发后,React 可能会暂停当前任务去处理更高优先级的任务,导致事件对象所引用的 DOM 状态可能已经改变或失效,如果使用 e.persist() 持久化事件对象,容易引发数据不一致或内存泄漏问题,因此 React 更推荐在事件回调中立即提取所需数据,以确保数据的准确性和逻辑的可预测性。