React中的stopPropagation和preventDefault实践记录
作者:阿蓝灬
事件冒泡、捕获是DOM事件传播的核心机制,而stopPropagation和preventDefault是控制事件行为的关键方法。React的合成事件体系基于原生事件封装,但在表现上有显著差异。下面分三部分详细说明:
一、事件冒泡与捕获的区别
DOM事件传播遵循“事件流”模型,分为三个阶段(从外到内再到外),其中“捕获”和“冒泡”是核心阶段:
| 阶段 | 传播方向 | 触发顺序 | 作用 |
|---|---|---|---|
| 捕获阶段 | 从顶层元素(window)向目标元素传播 | 先执行 | 由外向内“捕获”事件,让上层元素有机会在事件到达目标前处理(如全局拦截)。 |
| 目标阶段 | 事件到达实际触发的元素(目标元素) | 中间执行 | 目标元素的事件处理函数触发。 |
| 冒泡阶段 | 从目标元素向顶层元素(window)传播 | 后执行 | 由内向外“冒泡”事件,让上层元素有机会在事件离开目标后处理(如事件委托)。 |
示例:
<div id="grandparent">
<div id="parent">
<button id="child">点击</button>
</div>
</div>点击child按钮时,事件流顺序为:
- 捕获阶段:
window → document → grandparent → parent → child(从外到内); - 目标阶段:
child(事件到达目标); - 冒泡阶段:
child → parent → grandparent → document → window(从内到外)。
核心区别:
- 传播方向相反:捕获是“自上而下”,冒泡是“自下而上”;
- 触发时机不同:捕获阶段的事件处理函数先于冒泡阶段执行(若同时绑定)。
二、stopPropagation与preventDefault的作用
两者都是事件对象(event)的方法,但作用完全不同:
1.event.stopPropagation()
- 作用:阻止事件继续在事件流中传播(包括捕获和冒泡阶段)。
- 效果:事件到达当前元素后,不会再向其他元素传播(无论是上层还是下层)。
示例:
若parent在冒泡阶段绑定事件,且child的事件处理中调用stopPropagation(),则点击child时,parent和grandparent的冒泡事件不会触发。
2.event.preventDefault()
- 作用:阻止事件的“默认行为”(浏览器为某些事件预设的行为)。
- 不影响:事件的传播(捕获和冒泡会正常进行)。
常见默认行为:
<a>标签点击跳转;<form>表单提交后刷新页面;- 右键点击弹出上下文菜单。
示例:<a href="https://example.com" onclick="event.preventDefault()">链接</a>
点击链接时,不会跳转(默认行为被阻止),但事件仍会正常冒泡到父元素。
三、React合成事件体系下的表现
React的“合成事件”(SyntheticEvent)是对原生DOM事件的封装,目的是统一跨浏览器的事件行为,并通过“事件委托”优化性能。其表现与原生事件有以下核心差异:
1. 事件委托机制
React不会将事件直接绑定到DOM元素上,而是将所有事件委托到根节点(React 17前是document,17后是挂载的根节点,如#root)。当事件触发并冒泡到根节点时,React再根据事件源分发到对应的组件处理函数。
2. 捕获阶段的处理方式
- 原生事件:通过
addEventListener(event, handler, true)的第三个参数true绑定捕获阶段的处理函数。 - React合成事件:默认在冒泡阶段处理事件;若需在捕获阶段处理,需在事件名后加
Capture后缀(如onClickCapture而非onClick)。
示例:
// 父组件在捕获阶段处理事件
<div onClickCapture={() => console.log('父元素捕获')}>
<button onClick={() => console.log('子元素冒泡')}>点击</button>
</div>
// 点击按钮时,输出顺序:父元素捕获 → 子元素冒泡(符合捕获先于冒泡的规则)
3.stopPropagation()的差异
- 原生事件:调用后会阻止事件在整个DOM树中的传播(包括到达React的委托根节点)。
- React合成事件:调用
e.stopPropagation()只能阻止合成事件的传播(即其他React组件的事件处理函数不会触发),但无法阻止原生事件的传播(因为原生事件已经冒泡到了根节点,React只是在此时分发合成事件)。
反例:
// 子组件(合成事件)
<button
onClick={(e) => {
e.stopPropagation(); // 阻止合成事件传播
console.log('子元素合成事件');
}}
// 原生事件
ref={(el) => {
el?.addEventListener('click', () => console.log('子元素原生事件'));
}}
>
点击
</button>
// 父组件(合成事件)
<div onClick={() => console.log('父元素合成事件')}>
{/* 子组件 */}
</div>点击按钮时:
- 子元素的合成事件和原生事件都会触发;
- 父元素的合成事件不会触发(因为合成事件的传播被阻止);
- 若父元素同时绑定了原生事件(如
addEventListener),则会触发(因为原生事件的传播未被阻止)。
4.preventDefault()的差异
- 作用与原生一致:阻止事件的默认行为(如表单提交、链接跳转)。
- 注意点:React中不能通过
return false同时实现阻止默认行为和传播(与原生DOM不同)。原生中return false等价于同时调用preventDefault()和stopPropagation(),但React中return false无效,必须显式调用对应方法。
5. 事件池机制(React 17前)
React 17之前,合成事件对象会被放入“事件池”复用(性能优化),事件处理函数执行完后,事件对象的属性会被清空。因此,异步访问事件属性会失效(需用e.persist()保留)。
React 17后移除了事件池,事件对象不再被复用,无需e.persist()。
总结
| 场景 | 原生事件 | React合成事件 |
|---|---|---|
| 传播阶段处理 | addEventListener(..., true)绑定捕获 | 事件名加Capture后缀(如onClickCapture) |
stopPropagation() | 阻止所有阶段的传播(包括到React根节点) | 仅阻止合成事件传播,不影响原生事件传播 |
preventDefault() | 阻止默认行为,不影响传播 | 同原生,需显式调用(return false无效) |
| 事件绑定方式 | 直接绑定到DOM元素 | 委托到根节点,通过组件函数分发 |
理解这些差异的核心是:React合成事件是“模拟”原生事件的抽象层,其传播机制依赖原生事件的冒泡,但行为上做了统一和限制,以适配组件化开发需求。
扩展:stopPropagation, preventDefault 和 return false 的区别
stopPropagation, preventDefault 和 return false 的区别
因为有父,子节点同在; 因为有监听事件和浏览器默认动作之分。使用 JavaScript 时为了达到预期效果经常需要阻止事件和动作执行. 一般我们会用到三种方法, 分别是 stopPropagation(), preventDefault() 和 return false。它们之间有什么区别, 该何时使用呢? 将在本文中进行讲解。
监听事件: 指在节点上能被监听的页面操作。如: select 节点的 change 事件, a 节点的 click 事件。
浏览器默认动作: 指特定页面元素上带有的功能。如: 点击 a 链接节点的跳转动作, 表单提交动作。
1.stopPropagation()
因为事件可以在各层级的节点中传递,不管是冒泡还是捕获,有时我们希望事件在特定节点执行完之后不再传递,可以使用事件对象的 stopPropagation() 方法。
假设页面上存在一个浮动弹出层,显示在最前面,当点击弹出层以外页面区域时,隐藏弹出层。为了做到这样的效果,我们会监听 documentElement 的 click 事件,一旦事件被触发即隐藏弹出层。但是...
这显然存在问题。当用户点击弹出层时,我们不希望它隐藏掉。但因为事件的冒泡传递,documentElement 的 click 事件也会被触发。这个时候,我们可以监听弹出层的 click 事件,并使用 stopPropagation() 方法阻止冒泡。请参考下面的代码。
// 在弹出对话框上点击时, 不进行任何页面操作, 并阻止冒泡
document.getElementById('dialog').onclick = function(ev) {
ev.stopPropagation();
};
// 在 documentElement 节点上监听到点击事件时, 隐藏对话框
document.documentElement.onclick = function(ev) {
document.getElementById('dialog').style.display = 'none';
};stopPropagation() 相当好用,可是 IE8 及以前版本都不支持(用jquery就不用考略这点了)。IE 的事件对象包含特有的属性 cancelBubble, 只要将它赋值为 false 即可阻止事件继续。如:
// 在弹出对话框上点击时, 不进行任何页面操作, 并阻止冒泡
document.getElementById('dialog').onclick = function(ev) {
ev.cancelBubble = false;
};preventDefault()
一个带事件监听的链接代码如下:
<a href="http://w3c.org" οnclick="alert('my name is chic!');">点击我哦!</a>点击该链接, 显示对话框后跳转页面。由此可知,除了执行监听事件还会触发浏览器默认动作;执行监听事件在前,触发浏览器默认动作在后。
这里有个经典示例, 我们希望点击链接在新窗口打开页面,但不希望当前页面跳转。这个时候可以使用 preventDefault() 阻止后面将要执行的浏览器默认动作。
<a id="link" href="http://w3c.org">W3C 首页链接</a>
<script>
//在新窗口, 打开页面
document.getElementById('link').onclick = function(ev) {
// 阻止浏览器默认动作 (页面跳转)
ev.preventDefault();
// 在新窗口打开页面
window.open(this.href);
};
</script>return false
退出执行,return false 之后的所有触发事件和动作都不会被执行。有时候 return false 可以用来替代 stopPropagation() 和 preventDefault(),比如我们上面新窗口打开链接的例子,如:
<a id="link" href="http://w3c.org">W3C 首页链接</a>
<script>
//在新窗口, 打开页面
document.getElementById('link').onclick = function(ev) {
// 在新窗口打开页面
window.open(this.href);
// 退出执行 (在监听事件之后执行的浏览器默认动作将不会被执行)
return false;
};
</script>有人认为 return false = stopPropagation() + preventDefault(),其实是错的。return false 不但阻止事件,还可以返回对象,跳出循环等... 方便地一刀切而不够灵活,滥用易出错.
到此这篇关于React中的stopPropagation和preventDefault实践记录的文章就介绍到这了,更多相关React stopPropagation和preventDefault内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
