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 面试场景。

相关推荐
没有鸡汤吃不下饭1 小时前
告别手动对接口:我用 OpenAPI JSON 做了一个前端接口同步 Skill
前端·ai编程
空栈独白1 小时前
NestJS实战-前后端联调
前端
米饭同学i1 小时前
浏览器记住密码导致忘记密码页面输入框回显错乱?看这篇就够了
前端
孤舟望月1 小时前
NestJS实战-后端开发-全局配置
前端
陆枫Larry1 小时前
从一个按钮间距,聊透 CSS 的 gap 属性
前端
北冥有鱼1 小时前
mqtt 测试
前端·后端
张鑫旭2 小时前
都AI时代了,我为何还在学习前端基础知识?
前端
swipe2 小时前
正则表达式入门到进阶:从表单校验到手写模板引擎
前端·javascript·面试
阿祖zu2 小时前
别再优化 RAG 了,适配 Agent 的 LLM Wiki 知识库理念
前端·后端·aigc