React 合成事件机制、和原生事件区别、事件冒泡阻止

这是 React 面试中的高频题,尤其是中高级前端岗位。可以从 React 合成事件机制 → 与原生事件区别 → 阻止冒泡和默认行为 三部分回答。


一、什么是 React 合成事件(SyntheticEvent)

React 为了兼容不同浏览器的事件行为,实现了一套自己的事件系统,称为 合成事件(SyntheticEvent)

例如:

javascript 复制代码
function App() {
  const handleClick = (e) => {
    console.log(e);
  };

  return <button onClick={handleClick}>点击</button>;
}

这里的 e 不是浏览器原生 Event,而是 React 封装后的 SyntheticEvent

本质:

css 复制代码
SyntheticEvent {
  nativeEvent: MouseEvent,
  target,
  currentTarget,
  stopPropagation,
  preventDefault
}

React 内部会:

  1. 监听原生事件
  2. 封装成 SyntheticEvent
  3. 统一派发给对应组件

好处:

  • 抹平浏览器差异
  • 提高兼容性
  • 方便 React 统一管理事件

二、React 合成事件工作机制

React16及以前

React 采用 事件委托(Event Delegation)

例如:

xml 复制代码
<div>
  <button onClick={fn1}>按钮1</button>
  <button onClick={fn2}>按钮2</button>
</div>

实际上不会给每个 button 绑定 click。

React 会:

javascript 复制代码
document.addEventListener('click', ...)

统一绑定到 document。

触发时:

css 复制代码
button点击
    ↓
document监听到
    ↓
React找到对应Fiber节点
    ↓
执行onClick

优势:

  • 减少事件监听器数量
  • 节省内存
  • 提高性能

React17+

React 修改了事件委托位置。

以前:

javascript 复制代码
document

现在:

复制代码
root容器

例如:

javascript 复制代码
ReactDOM.createRoot(
  document.getElementById('root')
);

事件绑定到:

bash 复制代码
<div id="root"></div>

而不是:

javascript 复制代码
document

原因:

解决多个 React 应用共存时的事件冲突问题。


三、React 合成事件执行流程

假设:

xml 复制代码
<div onClick={parent}>
  <button onClick={child}>
    点击
  </button>
</div>

点击按钮:

markdown 复制代码
原生事件触发
     ↓
React收集事件
     ↓
捕获阶段执行
     ↓
目标阶段执行
     ↓
冒泡阶段执行

执行顺序:

scss 复制代码
child()
parent()

与原生 DOM 一致。


四、React合成事件和原生事件区别

1. 事件对象不同

React:

ini 复制代码
onClick={(e)=>{
  console.log(e);
}}

得到:

复制代码
SyntheticEvent

原生:

javascript 复制代码
button.addEventListener('click',(e)=>{
  console.log(e);
})

得到:

复制代码
MouseEvent

2. 获取原生事件方式不同

React:

ini 复制代码
const handleClick = (e) => {
  console.log(e.nativeEvent);
};

输出:

复制代码
MouseEvent

3. 绑定方式不同

React:

ini 复制代码
<button onClick={handleClick}>

原生:

less 复制代码
button.addEventListener('click', handleClick)

4. 事件委托位置不同

React16:

javascript 复制代码
document

React17+:

复制代码
root节点

原生:

scss 复制代码
element.addEventListener(...)

直接绑定到当前元素。


5. 性能优化不同

原生:

yaml 复制代码
1000个按钮
1000个监听器

React:

复制代码
统一监听
事件委托

监听器数量更少。


五、事件冒泡

DOM事件传播流程:

复制代码
捕获阶段
   ↓
目标阶段
   ↓
冒泡阶段

例如:

ini 复制代码
<div onClick={() => console.log('父')}>
  <button onClick={() => console.log('子')}>
    点击
  </button>
</div>

输出:

复制代码
子
父

因为发生了冒泡。


六、阻止事件冒泡

React中

ini 复制代码
const handleChild = (e) => {
  e.stopPropagation();
  console.log('子');
};
javascript 复制代码
<div onClick={() => console.log('父')}>
  <button onClick={handleChild}>
    点击
  </button>
</div>

结果:

复制代码

父元素不会执行。


原生JS

javascript 复制代码
button.addEventListener('click', (e) => {
  e.stopPropagation();
});

同样效果。


七、阻止默认行为

例如:

ini 复制代码
<a href="https://google.com">
  跳转
</a>

阻止跳转:

ini 复制代码
const handleClick = (e) => {
  e.preventDefault();
};
ini 复制代码
<a href="https://google.com" onClick={handleClick}>
  跳转
</a>

八、React捕获阶段事件

React提供:

复制代码
onClickCapture

例如:

ini 复制代码
<div onClickCapture={() => console.log('父捕获')}>
  <button onClick={() => console.log('子冒泡')}>
    点击
  </button>
</div>

输出:

复制代码
父捕获
子冒泡

执行顺序:

markdown 复制代码
Capture
    ↓
Target
    ↓
Bubble

九、React18面试加分点

事件池(Event Pooling)

React16以前:

ini 复制代码
const handleClick = (e) => {
  setTimeout(() => {
    console.log(e.target);
  });
};

可能得到:

csharp 复制代码
null

因为 React 使用了事件池复用对象。

解决:

ini 复制代码
e.persist();

React17以后:

事件池机制已经移除。

javascript 复制代码
setTimeout(() => {
  console.log(e.target);
});

正常输出。


面试标准回答(2分钟版)

React 的事件机制采用 SyntheticEvent 合成事件,它是 React 对浏览器原生事件的封装,用来抹平浏览器差异并统一管理事件。

React 并不会给每个 DOM 节点都绑定事件,而是采用 事件委托 。React16 及以前统一绑定到 document,React17 以后改为绑定到 React Root 容器,从而解决多个 React 应用之间的事件冲突问题。

合成事件对象提供了与原生事件一致的 API,比如 preventDefaultstopPropagation,同时可以通过 event.nativeEvent 获取原生事件对象。

事件传播机制与 DOM 一致,分为 捕获阶段、目标阶段、冒泡阶段。阻止冒泡可以使用:

ini 复制代码
e.stopPropagation();

阻止默认行为可以使用:

ini 复制代码
e.preventDefault();

React17 以后还移除了事件池机制,不再需要使用 e.persist()。这样回答基本可以覆盖大部分 React 面试场景。

相关推荐
程序员黑豆4 分钟前
鸿蒙应用开发之生命周期方法完全指南
前端·harmonyos
一点一木4 分钟前
第 23 届 ChinaJoy 刚结束,我把 965 个展台做成了 3D 云展馆
前端·人工智能
wordbaby6 分钟前
前端请求缓存写了,同一个接口为什么还是打了十几次?
前端·promise
不可能掉发7 分钟前
Env Guard:让浏览器一眼分清生产、测试和开发环境
前端·javascript·chrome·测试工具·html·开源软件·个人开发
饼干哥哥14 分钟前
Codex 必改的8 个基础配置
前端·人工智能·后端
ClouGence19 分钟前
AI Agent 能写测试、跑流程,为什么回归测试还不能完全交给 AI?
前端·测试
码云之上20 分钟前
Context Engineering:让 Agent 在当前步骤看到正确的事实
前端·人工智能·前端工程化
嘟嘟071740 分钟前
从手写 HashRouter 到 React Router:逐层拆解 SPA 前端路由的完整实现
前端·javascript
我叫蒙奇1 小时前
JS中的隐式类型转换
前端
他们叫我秃子1 小时前
前端开发转 Go 全栈(四):代码写在前面,却要最后执行?我终于搞懂了 defer
前端·后端·go