React中的stopPropagation和preventDefault實(shí)踐記錄
事件冒泡、捕獲是DOM事件傳播的核心機(jī)制,而stopPropagation和preventDefault是控制事件行為的關(guān)鍵方法。React的合成事件體系基于原生事件封裝,但在表現(xiàn)上有顯著差異。下面分三部分詳細(xì)說明:
一、事件冒泡與捕獲的區(qū)別
DOM事件傳播遵循“事件流”模型,分為三個(gè)階段(從外到內(nèi)再到外),其中“捕獲”和“冒泡”是核心階段:
| 階段 | 傳播方向 | 觸發(fā)順序 | 作用 |
|---|---|---|---|
| 捕獲階段 | 從頂層元素(window)向目標(biāo)元素傳播 | 先執(zhí)行 | 由外向內(nèi)“捕獲”事件,讓上層元素有機(jī)會(huì)在事件到達(dá)目標(biāo)前處理(如全局?jǐn)r截)。 |
| 目標(biāo)階段 | 事件到達(dá)實(shí)際觸發(fā)的元素(目標(biāo)元素) | 中間執(zhí)行 | 目標(biāo)元素的事件處理函數(shù)觸發(fā)。 |
| 冒泡階段 | 從目標(biāo)元素向頂層元素(window)傳播 | 后執(zhí)行 | 由內(nèi)向外“冒泡”事件,讓上層元素有機(jī)會(huì)在事件離開目標(biāo)后處理(如事件委托)。 |
示例:
<div id="grandparent">
<div id="parent">
<button id="child">點(diǎn)擊</button>
</div>
</div>點(diǎn)擊child按鈕時(shí),事件流順序?yàn)椋?/p>
- 捕獲階段:
window → document → grandparent → parent → child(從外到內(nèi)); - 目標(biāo)階段:
child(事件到達(dá)目標(biāo)); - 冒泡階段:
child → parent → grandparent → document → window(從內(nèi)到外)。
核心區(qū)別:
- 傳播方向相反:捕獲是“自上而下”,冒泡是“自下而上”;
- 觸發(fā)時(shí)機(jī)不同:捕獲階段的事件處理函數(shù)先于冒泡階段執(zhí)行(若同時(shí)綁定)。
二、stopPropagation與preventDefault的作用
兩者都是事件對(duì)象(event)的方法,但作用完全不同:
1.event.stopPropagation()
- 作用:阻止事件繼續(xù)在事件流中傳播(包括捕獲和冒泡階段)。
- 效果:事件到達(dá)當(dāng)前元素后,不會(huì)再向其他元素傳播(無論是上層還是下層)。
示例:
若parent在冒泡階段綁定事件,且child的事件處理中調(diào)用stopPropagation(),則點(diǎn)擊child時(shí),parent和grandparent的冒泡事件不會(huì)觸發(fā)。
2.event.preventDefault()
- 作用:阻止事件的“默認(rèn)行為”(瀏覽器為某些事件預(yù)設(shè)的行為)。
- 不影響:事件的傳播(捕獲和冒泡會(huì)正常進(jìn)行)。
常見默認(rèn)行為:
<a>標(biāo)簽點(diǎn)擊跳轉(zhuǎn);<form>表單提交后刷新頁面;- 右鍵點(diǎn)擊彈出上下文菜單。
示例:<a href="https://example.com" onclick="event.preventDefault()">鏈接</a>
點(diǎn)擊鏈接時(shí),不會(huì)跳轉(zhuǎn)(默認(rèn)行為被阻止),但事件仍會(huì)正常冒泡到父元素。
三、React合成事件體系下的表現(xiàn)
React的“合成事件”(SyntheticEvent)是對(duì)原生DOM事件的封裝,目的是統(tǒng)一跨瀏覽器的事件行為,并通過“事件委托”優(yōu)化性能。其表現(xiàn)與原生事件有以下核心差異:
1. 事件委托機(jī)制
React不會(huì)將事件直接綁定到DOM元素上,而是將所有事件委托到根節(jié)點(diǎn)(React 17前是document,17后是掛載的根節(jié)點(diǎn),如#root)。當(dāng)事件觸發(fā)并冒泡到根節(jié)點(diǎn)時(shí),React再根據(jù)事件源分發(fā)到對(duì)應(yīng)的組件處理函數(shù)。
2. 捕獲階段的處理方式
- 原生事件:通過
addEventListener(event, handler, true)的第三個(gè)參數(shù)true綁定捕獲階段的處理函數(shù)。 - React合成事件:默認(rèn)在冒泡階段處理事件;若需在捕獲階段處理,需在事件名后加
Capture后綴(如onClickCapture而非onClick)。
示例:
// 父組件在捕獲階段處理事件
<div onClickCapture={() => console.log('父元素捕獲')}>
<button onClick={() => console.log('子元素冒泡')}>點(diǎn)擊</button>
</div>
// 點(diǎn)擊按鈕時(shí),輸出順序:父元素捕獲 → 子元素冒泡(符合捕獲先于冒泡的規(guī)則)
3.stopPropagation()的差異
- 原生事件:調(diào)用后會(huì)阻止事件在整個(gè)DOM樹中的傳播(包括到達(dá)React的委托根節(jié)點(diǎn))。
- React合成事件:調(diào)用
e.stopPropagation()只能阻止合成事件的傳播(即其他React組件的事件處理函數(shù)不會(huì)觸發(fā)),但無法阻止原生事件的傳播(因?yàn)樵录呀?jīng)冒泡到了根節(jié)點(diǎn),React只是在此時(shí)分發(fā)合成事件)。
反例:
// 子組件(合成事件)
<button
onClick={(e) => {
e.stopPropagation(); // 阻止合成事件傳播
console.log('子元素合成事件');
}}
// 原生事件
ref={(el) => {
el?.addEventListener('click', () => console.log('子元素原生事件'));
}}
>
點(diǎn)擊
</button>
// 父組件(合成事件)
<div onClick={() => console.log('父元素合成事件')}>
{/* 子組件 */}
</div>點(diǎn)擊按鈕時(shí):
- 子元素的合成事件和原生事件都會(huì)觸發(fā);
- 父元素的合成事件不會(huì)觸發(fā)(因?yàn)楹铣墒录膫鞑ケ蛔柚梗?/li>
- 若父元素同時(shí)綁定了原生事件(如
addEventListener),則會(huì)觸發(fā)(因?yàn)樵录膫鞑ノ幢蛔柚梗?/li>
4.preventDefault()的差異
- 作用與原生一致:阻止事件的默認(rèn)行為(如表單提交、鏈接跳轉(zhuǎn))。
- 注意點(diǎn):React中不能通過
return false同時(shí)實(shí)現(xiàn)阻止默認(rèn)行為和傳播(與原生DOM不同)。原生中return false等價(jià)于同時(shí)調(diào)用preventDefault()和stopPropagation(),但React中return false無效,必須顯式調(diào)用對(duì)應(yīng)方法。
5. 事件池機(jī)制(React 17前)
React 17之前,合成事件對(duì)象會(huì)被放入“事件池”復(fù)用(性能優(yōu)化),事件處理函數(shù)執(zhí)行完后,事件對(duì)象的屬性會(huì)被清空。因此,異步訪問事件屬性會(huì)失效(需用e.persist()保留)。
React 17后移除了事件池,事件對(duì)象不再被復(fù)用,無需e.persist()。
總結(jié)
| 場(chǎng)景 | 原生事件 | React合成事件 |
|---|---|---|
| 傳播階段處理 | addEventListener(..., true)綁定捕獲 | 事件名加Capture后綴(如onClickCapture) |
stopPropagation() | 阻止所有階段的傳播(包括到React根節(jié)點(diǎn)) | 僅阻止合成事件傳播,不影響原生事件傳播 |
preventDefault() | 阻止默認(rèn)行為,不影響傳播 | 同原生,需顯式調(diào)用(return false無效) |
| 事件綁定方式 | 直接綁定到DOM元素 | 委托到根節(jié)點(diǎn),通過組件函數(shù)分發(fā) |
理解這些差異的核心是:React合成事件是“模擬”原生事件的抽象層,其傳播機(jī)制依賴原生事件的冒泡,但行為上做了統(tǒng)一和限制,以適配組件化開發(fā)需求。
擴(kuò)展:stopPropagation, preventDefault 和 return false 的區(qū)別
stopPropagation, preventDefault 和 return false 的區(qū)別
因?yàn)橛懈?子節(jié)點(diǎn)同在; 因?yàn)橛斜O(jiān)聽事件和瀏覽器默認(rèn)動(dòng)作之分。使用 JavaScript 時(shí)為了達(dá)到預(yù)期效果經(jīng)常需要阻止事件和動(dòng)作執(zhí)行. 一般我們會(huì)用到三種方法, 分別是 stopPropagation(), preventDefault() 和 return false。它們之間有什么區(qū)別, 該何時(shí)使用呢? 將在本文中進(jìn)行講解。
監(jiān)聽事件: 指在節(jié)點(diǎn)上能被監(jiān)聽的頁面操作。如: select 節(jié)點(diǎn)的 change 事件, a 節(jié)點(diǎn)的 click 事件。
瀏覽器默認(rèn)動(dòng)作: 指特定頁面元素上帶有的功能。如: 點(diǎn)擊 a 鏈接節(jié)點(diǎn)的跳轉(zhuǎn)動(dòng)作, 表單提交動(dòng)作。
1.stopPropagation()
因?yàn)槭录梢栽诟鲗蛹?jí)的節(jié)點(diǎn)中傳遞,不管是冒泡還是捕獲,有時(shí)我們希望事件在特定節(jié)點(diǎn)執(zhí)行完之后不再傳遞,可以使用事件對(duì)象的 stopPropagation() 方法。
假設(shè)頁面上存在一個(gè)浮動(dòng)彈出層,顯示在最前面,當(dāng)點(diǎn)擊彈出層以外頁面區(qū)域時(shí),隱藏彈出層。為了做到這樣的效果,我們會(huì)監(jiān)聽 documentElement 的 click 事件,一旦事件被觸發(fā)即隱藏彈出層。但是...
這顯然存在問題。當(dāng)用戶點(diǎn)擊彈出層時(shí),我們不希望它隱藏掉。但因?yàn)槭录拿芭輦鬟f,documentElement 的 click 事件也會(huì)被觸發(fā)。這個(gè)時(shí)候,我們可以監(jiān)聽彈出層的 click 事件,并使用 stopPropagation() 方法阻止冒泡。請(qǐng)參考下面的代碼。
// 在彈出對(duì)話框上點(diǎn)擊時(shí), 不進(jìn)行任何頁面操作, 并阻止冒泡
document.getElementById('dialog').onclick = function(ev) {
ev.stopPropagation();
};
// 在 documentElement 節(jié)點(diǎn)上監(jiān)聽到點(diǎn)擊事件時(shí), 隱藏對(duì)話框
document.documentElement.onclick = function(ev) {
document.getElementById('dialog').style.display = 'none';
};stopPropagation() 相當(dāng)好用,可是 IE8 及以前版本都不支持(用jquery就不用考略這點(diǎn)了)。IE 的事件對(duì)象包含特有的屬性 cancelBubble, 只要將它賦值為 false 即可阻止事件繼續(xù)。如:
// 在彈出對(duì)話框上點(diǎn)擊時(shí), 不進(jìn)行任何頁面操作, 并阻止冒泡
document.getElementById('dialog').onclick = function(ev) {
ev.cancelBubble = false;
};preventDefault()
一個(gè)帶事件監(jiān)聽的鏈接代碼如下:
<a οnclick="alert('my name is chic!');">點(diǎn)擊我哦!</a>點(diǎn)擊該鏈接, 顯示對(duì)話框后跳轉(zhuǎn)頁面。由此可知,除了執(zhí)行監(jiān)聽事件還會(huì)觸發(fā)瀏覽器默認(rèn)動(dòng)作;執(zhí)行監(jiān)聽事件在前,觸發(fā)瀏覽器默認(rèn)動(dòng)作在后。
這里有個(gè)經(jīng)典示例, 我們希望點(diǎn)擊鏈接在新窗口打開頁面,但不希望當(dāng)前頁面跳轉(zhuǎn)。這個(gè)時(shí)候可以使用 preventDefault() 阻止后面將要執(zhí)行的瀏覽器默認(rèn)動(dòng)作。
<a id="link" >W3C 首頁鏈接</a>
<script>
//在新窗口, 打開頁面
document.getElementById('link').onclick = function(ev) {
// 阻止瀏覽器默認(rèn)動(dòng)作 (頁面跳轉(zhuǎn))
ev.preventDefault();
// 在新窗口打開頁面
window.open(this.href);
};
</script>return false
退出執(zhí)行,return false 之后的所有觸發(fā)事件和動(dòng)作都不會(huì)被執(zhí)行。有時(shí)候 return false 可以用來替代 stopPropagation() 和 preventDefault(),比如我們上面新窗口打開鏈接的例子,如:
<a id="link" >W3C 首頁鏈接</a>
<script>
//在新窗口, 打開頁面
document.getElementById('link').onclick = function(ev) {
// 在新窗口打開頁面
window.open(this.href);
// 退出執(zhí)行 (在監(jiān)聽事件之后執(zhí)行的瀏覽器默認(rèn)動(dòng)作將不會(huì)被執(zhí)行)
return false;
};
</script>有人認(rèn)為 return false = stopPropagation() + preventDefault(),其實(shí)是錯(cuò)的。return false 不但阻止事件,還可以返回對(duì)象,跳出循環(huán)等... 方便地一刀切而不夠靈活,濫用易出錯(cuò).
到此這篇關(guān)于React中的stopPropagation和preventDefault實(shí)踐記錄的文章就介紹到這了,更多相關(guān)React stopPropagation和preventDefault內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用 React Router Dom 實(shí)現(xiàn)路由導(dǎo)航的詳細(xì)過程
React Router Dom 是 React 應(yīng)用程序中用于處理路由的常用庫,它提供了一系列組件和 API 來管理應(yīng)用程序的路由,這篇文章主要介紹了使用 React Router Dom 實(shí)現(xiàn)路由導(dǎo)航,需要的朋友可以參考下2024-03-03
淺談react?16.8版本新特性以及對(duì)react開發(fā)的影響
本文主要介紹了react?16.8版本新特性以及對(duì)react開發(fā)的影響,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
每天一個(gè)hooks學(xué)習(xí)之useUnmount
這篇文章主要為大家介紹了每天一個(gè)hooks學(xué)習(xí)之useUnmount,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
IntersectionObserver實(shí)現(xiàn)加載更多組件demo
這篇文章主要為大家介紹了IntersectionObserver實(shí)現(xiàn)加載更多組件demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
在React中如何優(yōu)雅的處理事件響應(yīng)詳解
這篇文章主要給大家介紹了關(guān)于在React中如何優(yōu)雅處理事件響應(yīng)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07

