1. 程式人生 > 程式設計 >react合成事件與原生事件的相關理解

react合成事件與原生事件的相關理解

1. 原生事件

原生事件就是js的原生事件,如通過document.addEventListener來設定的監聽事件。

在react中即使有自己的一套事件機制(見下面合成事件),但有時候的業務場景我們仍然需要使用原生事件。比如我們封裝一程式設計客棧個Modal彈窗元件,需要在點選非彈窗www.cppcns.com區域時關掉彈窗,此時我們只能針對document進行原生點選事件監聽。

由於原生事件需要繫結在真實DOM上,所以一般是在componentDidMount階段或者元件/元素的ref的函式執行階段進行繫結操作,並且注意要在componentWillUnmount階段進行解綁操作以避免記憶體洩漏。

2. 合成事件

React有自己的一套事件機制,它重新封裝了絕大部分的原生事件。合成事件採用了事件池,這樣做可以大大節省記憶體,而不會頻繁的建立和銷燬事件物件。

在React中,如果需要繫結事件,我們常常在jsx中這麼寫:

handleClick(){
}
<div onClick={this.handleClick.bind(this)}>
	react事件
</div>

大致原理:

React並不是將click事件綁在該div的真實DOM上,而是在document處監聽所有支援的事件,當事件發生並冒泡至document處時,React將事件內容封裝並交由真正的處理函式執行。

以上面的程式碼為例,整個事件生命週期示意如下:

react合成事件與原生事件的相關理解

合成事件的一些特點總結:

  • React 上註冊的事件最終會繫結在document這個 DOM 上,而不是 React 元件對應的 DOM(減少記憶體開銷就是因為所有的事件都繫結在 document 上,其他節點沒有繫結事件)
  • React 通過佇列的形式,從觸發的元件向父元件回溯,然後呼叫他們 JSX 中定義的 callback
  • React 通過物件池的形式管理合成事件物件的建立和銷燬,減少了垃圾的生成和新物件記憶體的分配,提高了效能

瞭解react合成事件的大概原理後,方便我們解答下面一個問題:

為什麼react事件需要手動繫結this

合成事件觸發之後會冒泡一路到document的節點,然後開始分發document節點收集到的事件,這個時候react從事件觸發的元件例項開始, 遍歷虛擬dom樹,從樹上取下我們繫結的事件,收集起來,然後執行。舉個例子:

class Test extends React.Component {
   fatherHandler =  function father() { /*...*/}
   childHander = function child() {/*...*/}

   render(){
     return (
       <div onClick={this.fatherHandler}>
         <span onClick={this.childHander}>
         </span>
       </div>
     );
   }

}

當事件觸發以後react會把上面的事件處理函式放到一個數組裡是這樣的

[father,child]

最後,react只要遍歷執行這個陣列,就能執行所有需要執行的事件處理函式。這裡react對函式進行了臨時儲存,這個時候執行的話,this自然就丟失了。

如果react儲存順便儲存一下例項,還是可以做到,不需要你繫結this的,但是這樣對於react來說代價太大了。

3. 原生與合成事件觸發順序

  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'http://www.cppcns.com);
  }

  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>)
http://www.cppcns.com  }


點選child中的test後,事件觸發順序如下:

react合成事件與原生事件的相關理解

結論:

無論是否是對於同一元素監聽的同種型別事件,原生事件總是比合成事件先觸發。這是由於上面我們說到的合成事件最終都會繫結到documnet DOM上導致的,當合成事件監聽到後,總是冒泡到document才會真正觸發。 而documnet DOM上監聽的原生事件則總是最後觸發

4. 合成事件和原生事件混用

react合成事件和原生事件最好不要混用。

原生事件中如果執行了stopPropagation(阻止冒泡)方法,則很容易導致其他同類型react合成事件失效。因為這樣所有同級以及後代元素的合成事件和原生事件都將無法冒泡到document上。

而如果僅僅是合成事件中使用了e.stopPropagation(阻止冒泡)方法,則不會影響原生事件的冒泡

相關疑問:

我們知道React事件監聽器中獲得的入參並不是瀏覽器原生事件,原生事件可以通過e.nativeEvent來獲取。通過這種方式,合成事件可以影響原生事件嗎?

e.nativeEvent.stopPropagation

即使在react的合成事件中呼叫原生事件的阻止冒泡,實際作用是在DOM最外層阻止冒泡,並不符合預期。也就是說它最終只能控制當前監聽的合成事件不會冒泡到document DOM的原生事件

e.nativeEvent.stopImmediatePropagation

該方法與上面的nativeEvent.stopPropagation有類似的功能,都可阻止當前監聽的合成事件冒泡到document DOM的程式設計客棧原生事件

stopImmediatePropagation常常在多個第三方庫混用時,用來阻止多個事件監聽器中的非必要執行。比如同一個元素的同種事件,設定了多個監聽事件函式,則該方式可以控制監聽函式只觸發第一個

stopImmediatePropagation和stopPropagation本都是原生事件,但在React自己的事件體系中,重新封裝了後者,卻沒有封裝前者。導致在合成事件中只能手動呼叫nativeEvent.stopImmediatePropagation。

因為在React的合成事件機制中,一個元件只能繫結一個同類型的事件監聽器(重複定義時,後面的監聽器會覆蓋之前的),所以合成事件無需去封裝stopImmediatePropagation。

所以,在React的合成事件中,e.nativeEvent.stopPropagation和e.nativeEvent.stopImmediatePropagation實際的作用是等價的

此外,由於事件繫結的順序問題,需要注意,如果是在react-dom.js載入前繫結的document原生事件,stopImmediatePropagation也是無法阻止的。

以上就是react合成事件與原生事件的相關理解的詳細內容,更多關於react合成事件與原生事件的資料請關注我們其它相關文章!