前言
做項目過程中, 發現了一個問題,onClik阻止冒泡事件並沒有生效,僅僅只是阻止outClick,我的需求是隻觸發"inner dom click",(PS:不想知道過程的可以直接跳過看最後的結論)
其他前端有趣的例子和坑合集:https://github.com/wqhui/CodeDemo
代碼鏈接:https://codepen.io/Lik_Lit/pen/OJLROMW
下面代碼的輸出是
“root click”
“inner dom click”
“document click”
const domContainer = document.querySelector('#root')
// 綁定在外層的點擊 非documemt層的原生事件
domContainer.addEventListener('click', e => console.log('root click'))
class InnerDom extends React.Component {
componentDidMount () {
// documemt層的原生事件
document.addEventListener('click', () => {
console.log('document click')
})
}
outClick = (e) => {
console.log('out dom click')
}
innerClick = (e) => {
e.stopPropagation();
console.log('inner dom click')
}
render () {
return <div onClick={this.outClick}>
<button onClick={this.innerClick}> 測試冒泡</button>
</div>
}
}
ReactDOM.render(<InnerDom />, domContainer)
解決過程與坑
一尋思想起來React組件類似onClick的事件是合成事件,本質上所有綁定是代理到document上的,所有綁定都會冒泡到document層去執行。類似下面:
// react所有合成事件, SyntheticEvent裏面執行回調函數
document.addEventListener('click', SyntheticEvent);
// 瀏覽器原生
document.addEventListener('click', () => {
alert('document click');
})
所以在React綁定的合成事件調用e.stopPropagation()
阻止的只是React裏綁定的合成事件,比如我例子裏面的outClick就沒有觸發。而對於原生事件還是阻止不了
百度告訴我說e.nativeEvent.stopImmediatePropagation
可以阻止冒泡到原生事件,我就在在innerClick事件里加上了這句,輸出變成了
“root click”
“inner dom click”
顯然還是不可以,因爲stopImmediatePropagation
只是阻止同層級且綁定靠後的事件(具體參考MDN),非document層的原生事件還是沒有阻止,因爲React的事件都是綁定在document層,所以React要阻止冒泡事件只有使用原生的綁定去調用e.stopPropagation()
refCb = (dom) => {
//這裏我就沒有去解綁了
dom && dom.addEventListener('click',this.innerClick)
}
<button ref={this.refCb} > 測試冒泡</button>
這樣就可以解決了
結論
- React組件綁定事件是合成事件,本質上是代理到document上,採用事件冒泡的形式冒泡到document上面,然後React將事件封裝給正式的函數處理運行和處理(可以理解成React所有事件都是綁定在document層)
- 對於React的合成事件對象e,
e.stopPropagation()
只能阻止React合成事件的冒泡,e.nativeEvent.stopImmediatePropagation
只能用來阻止冒泡到直接綁定在document上的事件 - 要想阻止所有的冒泡事件,只能通過ref獲得dom節點監聽,用原生事件對象e的
e.stopPropagation()
去阻止冒泡