亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

React中阻止事件冒泡的問(wèn)題詳析

 更新時(shí)間:2019年04月12日 11:41:30   作者:劉哇勇  
這篇文章主要給大家介紹了關(guān)于React中阻止事件冒泡問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用React具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

最近在研究react、redux等,網(wǎng)上找了很久都沒(méi)有完整的答案,索性自己整理下,這篇文章就來(lái)給大家介紹了關(guān)于React阻止事件冒泡的相關(guān)內(nèi)容,下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧

在正式開(kāi)始前,先來(lái)看看 JS 中事件的觸發(fā)與事件處理器的執(zhí)行。

JS 中事件的監(jiān)聽(tīng)與處理

事件捕獲與冒泡

DOM 事件會(huì)先后經(jīng)歷 捕獲 與 冒泡 兩個(gè)階段。捕獲即事件沿著 DOM 樹(shù)由上往下傳遞,到達(dá)觸發(fā)事件的元素后,開(kāi)始由下往上冒泡。

IE9 及之前的版本只支持冒泡

                  |  A
 -----------------|--|-----------------
 | Parent         |  |                |
 |   -------------|--|-----------     |
 |   |Children    V  |          |     |
 |   ----------------------------     |
 |                                    |
 --------------------------------------

事件處理器

默認(rèn)情況下,事件處理器是在事件的冒泡階段執(zhí)行,無(wú)論是直接設(shè)置元素的 onclick 屬性還是通過(guò) EventTarget.addEventListener() 來(lái)綁定,后者在沒(méi)有設(shè)置 useCapture 參數(shù)為 true 的情況下。

考察下面的示例:

<button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
 document.addEventListener("click", function(event) {
 console.log("document clicked");
 });

 function btnClickHandler(event) {
 console.log("btn clicked");
 }
</script>

輸出:

btn clicked
document clicked

阻止事件的冒泡

通過(guò)調(diào)用事件身上的 stopPropagation() 可阻止事件冒泡,這樣可實(shí)現(xiàn)只我們想要的元素處理該事件,而其他元素接收不到。

<button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
 document.addEventListener(
 "click",
 function(event) {
 console.log("document clicked");
 },
 false
 );

 function btnClickHandler(event) {
 event.stopPropagation();
 console.log("btn clicked");
 }
</script>

輸出:

btn clicked

一個(gè)阻止冒泡的應(yīng)用場(chǎng)景

常見(jiàn)的彈窗組件中,點(diǎn)擊彈窗區(qū)域之外關(guān)閉彈窗的功能,可通過(guò)阻止事件冒泡來(lái)方便地實(shí)現(xiàn),而不用這種方式的話,會(huì)引入復(fù)雜的判斷當(dāng)前點(diǎn)擊坐標(biāo)是否在彈窗之外的復(fù)雜邏輯。

document.addEventListener("click", () => {
 // close dialog
});

dialogElement.addEventListener("click", event => {
 event.stopPropagation();
});

但如果你嘗試在 React 中實(shí)現(xiàn)上面的邏輯,一開(kāi)始的嘗試會(huì)讓你懷疑人生。

React 下事件執(zhí)行的問(wèn)題

了解了 JS 中事件的基礎(chǔ),一切都沒(méi)什么難的。在引入 React 后,,事情開(kāi)始起變化。將上面阻止冒泡的邏輯在 React 里實(shí)現(xiàn)一下,代碼大概像這樣:

function App() {
 useEffect(() => {
 document.addEventListener("click", documentClickHandler);
 return () => {
 document.removeEventListener("click", documentClickHandler);
 };
 }, []);

 function documentClickHandler() {
 console.log("document clicked");
 }

 function btnClickHandler(event) {
 event.stopPropagation();
 console.log("btn clicked");
 }

 return <button onClick={btnClickHandler}>CLICK ME</button>;
}

輸出:

btn clicked
document clicked

document 上的事件處理器正常執(zhí)行了,并沒(méi)有因?yàn)槲覀冊(cè)诎粹o里面調(diào)用 event.stopPropagation() 而阻止。

那么問(wèn)題出在哪?

React 中事件處理的原理

考慮下面的示例代碼并思考點(diǎn)擊按鈕后的輸出。

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

window.addEventListener("click", event => {
 console.log("window");
});

document.addEventListener("click", event => {
 console.log("document:bedore react mount");
});

document.body.addEventListener("click", event => {
 console.log("body");
});

function App() {
 function documentHandler() {
 console.log("document within react");
 }

 useEffect(() => {
 document.addEventListener("click", documentHandler);
 return () => {
 document.removeEventListener("click", documentHandler);
 };
 }, []);

 return (
 <div
 onClick={() => {
 console.log("raect:container");
 }}
 >
 <button
 onClick={event => {
  console.log("react:button");
 }}
 >
 CLICK ME
 </button>
 </div>
 );
}

ReactDOM.render(<App />, document.getElementById("root"));

document.addEventListener("click", event => {
 console.log("document:after react mount");
});

現(xiàn)在對(duì)代碼做一些變動(dòng),在 body 的事件處理器中把冒泡阻止,再思考其輸出。

document.body.addEventListener("click", event => {
+ event.stopPropagation();
 console.log("body");
});

下面是劇透環(huán)節(jié),如果你懶得自己實(shí)驗(yàn)的話。

點(diǎn)擊按鈕后的輸出:

body
document:bedore react mount
react:button
raect:container
document:after react mount
document within react
window

bdoy 上阻止冒泡后,你可能會(huì)覺(jué)得,既然 body 是按鈕及按鈕容器的父級(jí),那么按鈕及容器的事件會(huì)正常執(zhí)行,事件到達(dá) body 后, body 的事件處理器執(zhí)行,然后就結(jié)束了。 document 上的事件處理器一個(gè)也不執(zhí)行。

事實(shí)上,按鈕及按鈕容器上的事件處理器也沒(méi)執(zhí)行,只有 body 執(zhí)行了。

輸出:

body

通過(guò)下面的分析,你能夠完全理解上面的結(jié)果。

SyntheticEvent

React 有自身的一套事件系統(tǒng),叫作 SyntheticEvent。叫什么不重要,實(shí)現(xiàn)上,其實(shí)就是通過(guò)在 document 上注冊(cè)事件代理了組件樹(shù)中所有的事件(facebook/react#4335),并且它監(jiān)聽(tīng)的是 document 冒泡階段。你完全可以忽略掉 SyntheticEvent 這個(gè)名詞,如果覺(jué)得它有點(diǎn)讓事情變得高大上或者增加了一些神秘的話。

除了事件系統(tǒng),它有自身的一套,另外還需要理解的是,界面上展示的 DOM 與我們代碼中的 DOM 組件,也是兩樣?xùn)|西,需要在概念上區(qū)分開(kāi)來(lái)。

所以,當(dāng)你在頁(yè)面上點(diǎn)擊按鈕,事件開(kāi)始在原生 DOM 上走捕獲冒泡流程。React 監(jiān)聽(tīng)的是 document 上的冒泡階段。事件冒泡到 document 后,React 將事件再派發(fā)到組件樹(shù)中,然后事件開(kāi)始在組件樹(shù) DOM 中走捕獲冒泡流程。

現(xiàn)在來(lái)嘗試?yán)斫庖幌螺敵鼋Y(jié)果:

  • 事件最開(kāi)始從原生 DOM 按鈕一路冒泡到 body,body 的事件處理器執(zhí)行,輸出 body。注意此時(shí)流程還沒(méi)進(jìn)入 React。為什么?因?yàn)?React 監(jiān)聽(tīng)的是 document 上的事件。
  • 繼續(xù)往上事件冒泡到 document。
    • 事件到達(dá) document 之后,發(fā)現(xiàn) document 上面一共綁定了三個(gè)事件處理器,分別是代碼中通過(guò) document.addEventListener ReactDOM.render 前后調(diào)用的,以及一個(gè)隱藏的事件處理器,是 ReactDOM 綁定的,也就是前面提到的 React 用來(lái)代理事件的那個(gè)處理器。
    • 同一元素上如果對(duì)同一類型的事件綁定了多個(gè)處理器,會(huì)按照綁定的順序來(lái)執(zhí)行。
    • 所以 ReactDOM.render 之前的那個(gè)處理器先執(zhí)行,輸出 document:before react mount。
    • 然后是 React 的事件處理器。此時(shí),流程才真正進(jìn)入 React,走進(jìn)我們的組件。組件里面就好理解了,從 button 冒泡到 container,依次輸出。
    • 最后 ReactDOM.render 之后的那個(gè)處理器先執(zhí)行,輸出 document:after react mount
  • 事件完成了在 document 上的冒泡,往上到了 window,執(zhí)行相應(yīng)的處理器并輸出 window。

理解 React 是通過(guò)監(jiān)聽(tīng) document 冒泡階段來(lái)代理組件中的事件,這點(diǎn)很重要。同時(shí),區(qū)分原生 DOM 與 React 組件,也很重要。并且,React 組件上的事件處理器接收到的 event 對(duì)象也有別于原生的事件對(duì)象,不是同一個(gè)東西。但這個(gè)對(duì)象上有個(gè) nativeEvent 屬性,可獲取到原生的事件對(duì)象,后面會(huì)用到和討論它。

緊接著的代碼的改動(dòng)中,我們?cè)?body 上阻止了事件冒泡,這樣事件在 body 就結(jié)束了,沒(méi)有到達(dá) document,那么 React 的事件就不會(huì)被觸發(fā),所以 React 組件樹(shù)中,按鈕及容器就沒(méi)什么反應(yīng)。如果沒(méi)理解到這點(diǎn),光看表象還以為是 bug。

進(jìn)而可以理解,如果在 ReactDOM.render() 之前的的 document 事件處理器上將冒泡結(jié)束掉,同樣會(huì)影響 React 的執(zhí)行。只不過(guò)這里需要調(diào)用的不是 event.stopPropagation() ,而是 event.stopImmediatePropagation() 。

document.addEventListener("click", event => {
+ event.stopImmediatePropagation();
 console.log("document:bedore react mount");
});

輸出:

body
document:bedore react mount

stopImmediatePropagation 會(huì)產(chǎn)生這樣的效果,即,如果同一元素上同一類型的事件(這里是 click)綁定了多個(gè)事件處理器,本來(lái)這些處理器會(huì)按綁定的先后來(lái)執(zhí)行,但如果其中一個(gè)調(diào)用了 stopImmediatePropagation,不但會(huì)阻止事件冒泡,還會(huì)阻止這個(gè)元素后續(xù)其他事件處理器的執(zhí)行。

所以,雖然都是監(jiān)聽(tīng) document 上的點(diǎn)擊事件,但 ReactDOM.render() 之前的這個(gè)處理器要先于 React,所以 React 對(duì) document 的監(jiān)聽(tīng)不會(huì)觸發(fā)。

解答前面按鈕未能阻止冒泡的問(wèn)題

如果你已經(jīng)忘了,這是相應(yīng)的代碼及輸出。

到這里,已經(jīng)可以解答為什么 React 組件中 button 的事件處理器中調(diào)用 event.stopPropagation() 沒(méi)有阻止 document 的點(diǎn)擊事件執(zhí)行的問(wèn)題了。因?yàn)?button 事件處理器的執(zhí)行前提是事件達(dá)到 document 被 React 接收到,然后 React 將事件派發(fā)到 button 組件。既然在按鈕的事件處理器執(zhí)行之前,事件已經(jīng)達(dá)到 document 了,那當(dāng)然就無(wú)法在按鈕的事件處理器進(jìn)行阻止了。

問(wèn)題的解決

要解決這個(gè)問(wèn)題,這里有不止一種方法。

用 window 替換 document

來(lái)自 React issue 回答中提供的這個(gè)方法是最快速有效的。使用 window 替換掉 document 后,前面的代碼可按期望的方式執(zhí)行。

function App() {
 useEffect(() => {
+ window.addEventListener("click", documentClickHandler);
 return () => {
+  window.removeEventListener("click", documentClickHandler);
 };
 }, []);

 function documentClickHandler() {
 console.log("document clicked");
 }

 function btnClickHandler(event) {
 event.stopPropagation();
 console.log("btn clicked");
 }

 return <button onClick={btnClickHandler}>CLICK ME</button>;
}

這里 button 事件處理器上接到到的 event 來(lái)自 React 系統(tǒng),也就是 document 上代理過(guò)來(lái)的,所以通過(guò)它阻止冒泡后,事件到 document 就結(jié)束了,而不會(huì)往上到 window。

Event.stopImmediatePropagation()

組件中事件處理器接收到的 event 事件對(duì)象是 React 包裝后的 SyntheticEvent 事件對(duì)象。但可通過(guò)它的 nativeEvent 屬性獲取到原生的 DOM 事件對(duì)象。通過(guò)調(diào)用這個(gè)原生的事件對(duì)象上的 stopImmediatePropagation() 方法可達(dá)到阻止冒泡的目的。

function btnClickHandler(event) {
+ event.nativeEvent.stopImmediatePropagation();
 console.log("btn clicked");
}

至于原理,其實(shí)前面已經(jīng)有展示過(guò)。React 在 render 時(shí)監(jiān)聽(tīng)了 document 冒泡階段的事件,當(dāng)我們的 App 組件執(zhí)行時(shí),準(zhǔn)確地說(shuō)是渲染完成后(useEffect 渲染完成后執(zhí)行),又在 document 上注冊(cè)了 click 的監(jiān)聽(tīng)。此時(shí) document 上有兩個(gè)事件處理器了,并且組件中的這個(gè)順序在 React 后面。

當(dāng)調(diào)用 event.nativeEvent.stopImmediatePropagation() 后,阻止了 document 上同類型后續(xù)事件處理器的執(zhí)行,達(dá)到了想要的效果。

但這種方式有個(gè)缺點(diǎn)很明顯,那就是要求需要被阻止的事件是在 React render 之后綁定,如果在之前綁定,是達(dá)不到效果的。

通過(guò)元素自身來(lái)綁定事件處理器

當(dāng)繞開(kāi) React 直接通過(guò)調(diào)用元素自己身上的方法來(lái)綁定事件時(shí),此時(shí)走的是原生 DOM 的流程,都沒(méi)在 React 的流程里面。

function App() {
 const btnElement = useRef(null);
 useEffect(() => {
 document.addEventListener("click", documentClickHandler);
 if (btnElement.current) {
  btnElement.current.addEventListener("click", btnClickHandler);
 }

 return () => {
  document.removeEventListener("click", documentClickHandler);
  if (btnElement.current) {
  btnElement.current.removeEventListener("click", btnClickHandler);
  }
 };
 }, []);

 function documentClickHandler() {
 console.log("document clicked");
 }

 function btnClickHandler(event) {
 event.stopPropagation();
 console.log("btn clicked");
 }

 return <button ref={btnElement}>CLICK ME</button>;
}

很明顯這樣是能解決問(wèn)題,但你根本不會(huì)想要這樣做。代碼丑陋,不直觀也不易理解。

結(jié)論

注意區(qū)分 React 組件的事件及原生 DOM 事件,一般情況下,盡量使用 React 的事件而不要混用。如果必需要混用比如監(jiān)聽(tīng) document,window 上的事件,處理 mousemove,resize 等這些場(chǎng)景,那么就需要注意本文提到的順序問(wèn)題,不然容易出 bug。

相關(guān)資源

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • React中使用dnd-kit實(shí)現(xiàn)拖曳排序功能

    React中使用dnd-kit實(shí)現(xiàn)拖曳排序功能

    在這篇文章中,我將帶著大家一起探究React中使用dnd-kit實(shí)現(xiàn)拖曳排序功能,由于前陣子需要在開(kāi)發(fā) Picals 的時(shí)候,需要實(shí)現(xiàn)一些拖動(dòng)排序的功能,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • React?split實(shí)現(xiàn)分割字符串的使用示例

    React?split實(shí)現(xiàn)分割字符串的使用示例

    當(dāng)我們需要將一個(gè)字符串按照指定的分隔符進(jìn)行分割成數(shù)組時(shí),我們可以在組件的生命周期方法中使用split方法來(lái)實(shí)現(xiàn)這個(gè)功能,本文就來(lái)介紹一下,感興趣的可以了解下
    2023-10-10
  • React學(xué)習(xí)之JSX與react事件實(shí)例分析

    React學(xué)習(xí)之JSX與react事件實(shí)例分析

    這篇文章主要介紹了React學(xué)習(xí)之JSX與react事件,結(jié)合實(shí)例形式分析了React中JSX表達(dá)式、屬性、嵌套與react事件相關(guān)使用技巧,需要的朋友可以參考下
    2020-01-01
  • React中this丟失的四種解決方法

    React中this丟失的四種解決方法

    這篇文章主要給大家介紹了關(guān)于React中this丟失的四種解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用React具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • react-native 圓弧拖動(dòng)進(jìn)度條實(shí)現(xiàn)的示例代碼

    react-native 圓弧拖動(dòng)進(jìn)度條實(shí)現(xiàn)的示例代碼

    本篇文章主要介紹了react-native 圓弧拖動(dòng)進(jìn)度條實(shí)現(xiàn)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • 一文掌握React?組件樹(shù)遍歷技巧

    一文掌握React?組件樹(shù)遍歷技巧

    這篇文章主要為大家介紹了React?組件樹(shù)遍歷技巧的掌握,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • 詳解各版本React路由的跳轉(zhuǎn)的方法

    詳解各版本React路由的跳轉(zhuǎn)的方法

    這篇文章主要介紹了詳解各版本React路由的跳轉(zhuǎn)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • React 高階組件入門介紹

    React 高階組件入門介紹

    本篇文章主要介紹了React高階組件入門介紹,這篇文章中我們?cè)敿?xì)的介紹了什么是高階組件,如何使用高階組件,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • 關(guān)于antd tree和父子組件之間的傳值問(wèn)題(react 總結(jié))

    關(guān)于antd tree和父子組件之間的傳值問(wèn)題(react 總結(jié))

    這篇文章主要介紹了關(guān)于antd tree 和父子組件之間的傳值問(wèn)題,是小編給大家總結(jié)的一些react知識(shí)點(diǎn),本文通過(guò)一個(gè)項(xiàng)目需求實(shí)例代碼詳解給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-06-06
  • React操作真實(shí)DOM實(shí)現(xiàn)動(dòng)態(tài)吸底部的示例

    React操作真實(shí)DOM實(shí)現(xiàn)動(dòng)態(tài)吸底部的示例

    本篇文章主要介紹了React操作真實(shí)DOM實(shí)現(xiàn)動(dòng)態(tài)吸底部的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10

最新評(píng)論