react合成事件與原生事件的相關(guān)理解
1. 原生事件
原生事件就是js的原生事件,如通過document.addEventListener來設(shè)置的監(jiān)聽事件。
在react中即使有自己的一套事件機(jī)制(見下面合成事件),但有時候的業(yè)務(wù)場景我們?nèi)匀恍枰褂迷录?。比如我們封裝一個Modal彈窗組件,需要在點擊非彈窗區(qū)域時關(guān)掉彈窗,此時我們只能針對document進(jìn)行原生點擊事件監(jiān)聽。
由于原生事件需要綁定在真實DOM上,所以一般是在componentDidMount階段或者組件/元素的ref的函數(shù)執(zhí)行階段進(jìn)行綁定操作,并且注意要在componentWillUnmount階段進(jìn)行解綁操作以避免內(nèi)存泄漏。
2. 合成事件
React有自己的一套事件機(jī)制,它重新封裝了絕大部分的原生事件。合成事件采用了事件池,這樣做可以大大節(jié)省內(nèi)存,而不會頻繁的創(chuàng)建和銷毀事件對象。
在React中,如果需要綁定事件,我們常常在jsx中這么寫:
handleClick(){
}
<div onClick={this.handleClick.bind(this)}>
react事件
</div>
大致原理:
React并不是將click事件綁在該div的真實DOM上,而是在document處監(jiān)聽所有支持的事件,當(dāng)事件發(fā)生并冒泡至document處時,React將事件內(nèi)容封裝并交由真正的處理函數(shù)運行。
以上面的代碼為例,整個事件生命周期示意如下:

合成事件的一些特點總結(jié):
- React 上注冊的事件最終會綁定在document這個 DOM 上,而不是 React 組件對應(yīng)的 DOM(減少內(nèi)存開銷就是因為所有的事件都綁定在 document 上,其他節(jié)點沒有綁定事件)
- React 通過隊列的形式,從觸發(fā)的組件向父組件回溯,然后調(diào)用他們 JSX 中定義的 callback
- React 通過對象池的形式管理合成事件對象的創(chuàng)建和銷毀,減少了垃圾的生成和新對象內(nèi)存的分配,提高了性能
了解react合成事件的大概原理后,方便我們解答下面一個問題:
為什么react事件需要手動綁定this
合成事件觸發(fā)之后會冒泡一路到document的節(jié)點,然后開始分發(fā)document節(jié)點收集到的事件,這個時候react從事件觸發(fā)的組件實例開始, 遍歷虛擬dom樹,從樹上取下我們綁定的事件,收集起來,然后執(zhí)行。舉個例子:
class Test extends React.Component {
fatherHandler = function father() { /*...*/}
childHander = function child() {/*...*/}
render(){
return (
<div onClick={this.fatherHandler}>
<span onClick={this.childHander}>
</span>
</div>
);
}
}
當(dāng)事件觸發(fā)以后react會把上面的事件處理函數(shù)放到一個數(shù)組里是這樣的
[father, child]
最后,react只要遍歷執(zhí)行這個數(shù)組,就能執(zhí)行所有需要執(zhí)行的事件處理函數(shù)。這里react對函數(shù)進(jìn)行了臨時保存,這個時候執(zhí)行的話,this自然就丟失了。
如果react保存順便保存一下實例,還是可以做到,不需要你綁定this的,但是這樣對于react來說代價太大了。
3. 原生與合成事件觸發(fā)順序
componentDidMount() {
this.parent.addEventListener('click', (e) => {
console.log('dom parent');
})
this.child.addEventListener('click', (e) => {
console.log('dom child');
})
document.addEventListener('click', (e) => {
console.log('document');
})
}
childClick = (e) => {
console.log('react child');
}
parentClick = (e) => {
console.log('react parent');
}
render() {
return (
<div onClick={this.parentClick} ref={ref => this.parent = ref}>
<div onClick={this.childClick} ref={ref => this.child = ref}>
test
</div>
</div>)
}
點擊child中的test后,事件觸發(fā)順序如下:

結(jié)論:
無論是否是對于同一元素監(jiān)聽的同種類型事件,原生事件總是比合成事件先觸發(fā)。這是由于上面我們說到的合成事件最終都會綁定到documnet DOM上導(dǎo)致的,當(dāng)合成事件監(jiān)聽到后,總是冒泡到document才會真正觸發(fā)。 而documnet DOM上監(jiān)聽的原生事件則總是最后觸發(fā)
4. 合成事件和原生事件混用
react合成事件和原生事件最好不要混用。
原生事件中如果執(zhí)行了stopPropagation(阻止冒泡)方法,則很容易導(dǎo)致其他同類型react合成事件失效。因為這樣所有同級以及后代元素的合成事件和原生事件都將無法冒泡到document上。
而如果僅僅是合成事件中使用了e.stopPropagation(阻止冒泡)方法,則不會影響原生事件的冒泡
相關(guān)疑問:
我們知道React事件監(jiān)聽器中獲得的入?yún)⒉⒉皇菫g覽器原生事件,原生事件可以通過e.nativeEvent來獲取。通過這種方式,合成事件可以影響原生事件嗎?
e.nativeEvent.stopPropagation
即使在react的合成事件中調(diào)用原生事件的阻止冒泡,實際作用是在DOM最外層阻止冒泡,并不符合預(yù)期。也就是說它最終只能控制當(dāng)前監(jiān)聽的合成事件不會冒泡到document DOM的原生事件
e.nativeEvent.stopImmediatePropagation
該方法與上面的nativeEvent.stopPropagation有類似的功能,都可阻止當(dāng)前監(jiān)聽的合成事件冒泡到document DOM的原生事件
stopImmediatePropagation常常在多個第三方庫混用時,用來阻止多個事件監(jiān)聽器中的非必要執(zhí)行。比如同一個元素的同種事件,設(shè)置了多個監(jiān)聽事件函數(shù),則該方式可以控制監(jiān)聽函數(shù)只觸發(fā)第一個
stopImmediatePropagation和stopPropagation本都是原生事件,但在React自己的事件體系中,重新封裝了后者,卻沒有封裝前者。導(dǎo)致在合成事件中只能手動調(diào)用nativeEvent.stopImmediatePropagation。
因為在React的合成事件機(jī)制中,一個組件只能綁定一個同類型的事件監(jiān)聽器(重復(fù)定義時,后面的監(jiān)聽器會覆蓋之前的),所以合成事件無需去封裝stopImmediatePropagation。
所以,在React的合成事件中,e.nativeEvent.stopPropagation和e.nativeEvent.stopImmediatePropagation實際的作用是等價的
此外,由于事件綁定的順序問題,需要注意,如果是在react-dom.js加載前綁定的document原生事件,stopImmediatePropagation也是無法阻止的。
以上就是react合成事件與原生事件的相關(guān)理解的詳細(xì)內(nèi)容,更多關(guān)于react合成事件與原生事件的資料請關(guān)注腳本之家其它相關(guān)文章!

