文章目錄
- 一、遇到的問題
-
- 問題描述
- 分析
- 二、React 合成事件
-
- 1.執行順序
- 2.合成事件阻止冒泡
-
- 2.1e.stopPropagation
- 2.2 e.nativeEvent.stopImmediatePropagation
- 3.this指向問題
-
-
- 解決
-
- 4.傳參
- 5. 事件池(React 17 不再使用事件池)
一、遇到的問題
React 阻止冒泡失效
問題描述
- 一個div彈框,點選按鈕時顯示,點選空白處隐藏
- 在document上綁定隐藏事件
- 點選顯示的按鈕需要阻止冒泡
- 但是我使用e.stopPropagation();失效
分析
- React元件類似onClick的事件是合成事件,本質上所有綁定是代理到document上的,所有綁定都會冒泡到document層去執行
- A、阻止合成事件間的冒泡,用e.stopPropagation();
- B、阻止合成事件與最外層document上的事件間的冒泡,用e.nativeEvent.stopImmediatePropagation();
- stopImmediatePropagation() 方法阻止監聽同一事件的其他事件監聽器被調用。
- 如果有多個相同類型事件的事件監聽函數綁定到同一個元素,當該類型的事件觸發時,它們會按照被添加的順序執行。如果其中某個監聽函數執行了 event.stopImmediatePropagation() 方法,則目前元素剩下的監聽函數将不會被執行。
- C、阻止合成事件與除最外層document上的原生事件上的冒泡,通過判斷e.target來避免
componentDidMount() {
document.body.addEventListener('click',e=>{
// 通過e.target判斷阻止冒泡
if(e.target&&e.target.matches('a')){
return;
}
console.log('body');
})
}
}
二、React 合成事件
React 元件綁定事件本質上是代理到 document 上。
也就是說,隻有當事件流冒泡到 document 上時,才會依次觸發 document 上綁定的事件。
- SyntheticEvent 執行個體将被傳遞給你的事件處理函數,它是浏覽器的原生事件的跨浏覽器包裝器。除相容所有浏覽器外,它還擁有和浏覽器原生事件相同的接口
- SyntheticEvent 是合并而來。這意味着 SyntheticEvent 對象可能會被重用,而且在事件回調函數被調用後,所有的屬性都會無效。出于性能考慮,你不能通過異步通路事件。
- 如果你想異步通路事件屬性,你需在事件上調用 event.persist(),此方法會從池中移除合成事件,允許使用者代碼保留對事件的引用。(從 v17 開始,e.persist() 将不再生效,因為 SyntheticEvent 不再放入事件池中。)
- 合成事件中的 currentTarget 指向目前 dom 元素,但是 nativeEvent 的 currentTarget 指向 document
1.執行順序
React 所有事件都挂載在 document 對象上;
當真實 DOM 元素觸發事件,會冒泡到 document 對象後,再處理 React 事件;
- 先執行原生事件,
- 然後處理 React 事件;
- 最後真正執行 document 上挂載的事件。
2.合成事件阻止冒泡
官網文檔描述了:
從 v0.14 開始,事件處理器傳回 false 時,不再阻止事件傳遞。你可以酌情手動調用 e.stopPropagation() 或
e.preventDefault() 作為替代方案。
也就是說,在 React 合成事件中,需要阻止冒泡時,可以使用 e.stopPropagation() 或 e.preventDefault() 方法來解決,另外還可以使用 e.nativeEvent.stopImmediatePropagation() 方法解決。
2.1e.stopPropagation
e.stopPropagation() 隻能阻止合成事件間冒泡,即下層的合成事件,不會冒泡到上層的合成事件。事件本身還都是在 document 上執行。是以最多隻能阻止 document 事件不能再冒泡到 window 上。
2.2 e.nativeEvent.stopImmediatePropagation
Event 接口的 stopImmediatePropagation() 方法阻止監聽同一事件的其他事件監聽器被調用。
在 React 中,e.nativeEvent 才是原生 DOM 事件的那個 event
該方法可以阻止監聽同一事件的其他事件監聽器被調用。
在 React 中,一個元件隻能綁定一個同類型的事件監聽器,當重複定義時,後面的監聽器會覆寫之前的。
事實上 nativeEvent 的 stopImmediatePropagation隻能阻止綁定在 document 上的事件監聽器。而合成事件上的 e.nativeEvent.stopImmediatePropagation() 能阻止合成事件不會冒泡到 document 上。
利用e.nativeEvent.stopImmediatePropagation解決我遇到的問題
點選div内部,由于不冒泡,會正常執行菜單點選。
點選div外部,執行document上事件,關閉div。
3.this指向問題
在 React 中,JSX 回調函數中的 this 經常會出問題,在 Class 中方法不會預設綁定 this,就會出現下面情況, this.funName 值為 undefined :
class App extends React.Component<any, any> {
childClickFun = () => {
console.log("React 事件");
};
clickFun() {
console.log("React this 指向問題", this.childClickFun); // undefined
}
render() {
return (
<div onClick={this.clickFun}>React this 指向問題</div>
);
}
}
export default App;
解決
1.用bind綁定this
<div onClick={this.clickFun.bind(this)}>React this 指向問題</div>
2.箭頭函數(this指向定義時所處的外層執行環境的this)
箭頭函數相關文章
clickFun() {
console.log("React this 指向問題", this.childClickFun); // undefined
}
render() {
return (
<div onClick={() => this.clickFun()}>React this 指向問題</div>
);
}
4.傳參
<div onClick={this.clickFun.bind(this,props)}>React this 指向問題</div>
或者
clickFun(props) {
console.log("React this 指向問題", this.childClickFun); // undefined
}
render() {
return (
<div onClick={() => this.clickFun(props)}>React this 指向問題</div>
);
}
5. 事件池(React 17 不再使用事件池)
React 事件池僅支援在 React 16 及更早版本中,在 React 17 已經不使用事件池。
下面以 React 16 版本為例:
function handleChange(e) {
console.log("原始資料:", e.target)
setTimeout(() => {
console.log("定時任務 e.target:", e.target); // null
console.log("定時任務:e:", e);
}, 100);
}
function App() {
return (
<div className="App">
<button onClick={handleChange}>測試事件池</button>
</div>
);
}
export default App;
可以看到輸出
在 React 16 及之前的版本,合成事件對象的事件處理函數全部被調用之後,所有屬性都會被置為 null 。這時,如果我們需要在事件處理函數運作之後擷取事件對象的屬性,可以使用 React 提供的 e.persist() 方法,保留所有屬性:
// 隻修改 handleChange 方法,其他不變
function handleChange(e) {
// 隻增加 persist() 執行
e.persist();
console.log("原始資料:", e.target)
setTimeout(() => {
console.log("定時任務 e.target:", e.target); // null
console.log("定時任務:e:", e);
}, 100);
}
再看結果
由于 Web 端的 React 17 不使用事件池,所有不會存在上述“所有屬性都會被置為 null”的問題。
參考文章
https://segmentfault.com/a/1190000038251163
本文連結https://blog.csdn.net/qq_39903567/article/details/115346338