重原理 - React中的事件處理原理和機制

React中的事件[擴展] - 非normal

這裏的事件指的是React內置的html組件中的事件

React官方當初在寫react的時候曾經思索。當用戶在react元素中綁定了事件以後, 如果react接管代碼以後在每一個真實的dom元素映射事件的話, 整個頁面中綁定的事件就會非常的多, 會影響整個頁面的執行效率, 所以經過深思熟慮以後, react團隊做出了以下的操作

  1. 用戶在react元素身上綁定事件以後, React會給document註冊事件
  2. 元素事件處理幾乎都在document的事件中處理(爲什麼說是幾乎, 因爲有些document上沒有的特殊事件就只能在元素上處理, 比如input框的onFocus和播放器的onPlay之類的, 可以去了解一下js知識)
  3. 在document的事件處理中, react會根據虛擬dom樹中結構完成事件函數的調用

我們來看一個有意思的例子

// 我們在App根組件中聲明一個react元素button, 並且給他註冊點擊事件, 同時我們又取到id爲root
// 的真實dom, 也給他註冊點擊事件, 按照邏輯來說, react元素button在root元素中, 那麼當我們點擊
// 按鈕的時候, 他會先觸發自身的點擊事件, 隨後冒泡到root點擊事件上導致root的點擊事件被觸發
export default class App extends React.PureComponent {
    render() {
        return (
            <button onClick = { () => { console.log('我是react元素, 我被點擊了') } }>click</button>
        )
    }
}

document.querySelector('#root').addEventListener('click', () => {
    console.log('我是真實dom - #root, 我被點擊了')
}, false)

上述操作輸出結果如下

在這裏插入圖片描述

我們再來看一個例子

import React from 'react';

// 組件A
function ChildA(props) {
    return (
        <div onClick = { () => {console.log('我是最外層的組件A')} }>我是最外層的組件A
            <ChildB />
        </div>
    )
}

// 組件B
function ChildB(props) {
    return (
        <div onClick = { () => {console.log('我是中層的組件B')} }>我是最外層的組件B
            <ChildC />
        </div>
    )
}


// 組件C
function ChildC(props) {
    return (
        <div onClick = { () => {console.log('我是最內層的組件C')} }>我是最內層的組件C</div>
    )
}

export default class App extends React.PureComponent {
    render() {
        return (
            <ChildA />
        )
    }
}

當我們點擊組件C的時候上述的操作輸出結果如下

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dsIqQNGP-1580891242014)('...')]

我們清晰的發現, 第一個例子#root的點擊是輸出在button點擊之前的, 第二個例子卻看似正常, 這種怪異的現象是因爲什麼呢?

在這裏插入圖片描述

這下可以發現, 正是因爲react會將react內置元素的點擊事件都註冊在document身上, 並且形成隊列觸發, 那麼第一個例子的時候我們肯定點擊的先是#root, 然後冒泡上的document, 所以也可以解釋爲什麼第一個例子發生的奇怪的現象了

那麼如果我們在某個真實dom元素的註冊事件中直接取消事件冒泡, 那麼react元素綁定的同類型事件將不再觸發

總結

  • 如果給真實的dom註冊了事件, 並且取消了事件冒泡, 那麼會導致react中相應的事件直接失效
  • 如果給真實的dom註冊了事件, 事件會先於react事件運行
  • react事件中的事件參數e並非真實的事件參數, 而是react進行合成後的對象
    1. 所以在react中的e.stopPropagation, 阻止的是虛擬dom中的事件冒泡, 而非真實dom的事件冒泡(實際上, 由於真實dom中react是給document綁定事件, 所以阻止document的冒泡毫無意義)
        // 組件A
    function ChildA(props) {
        return (
            <div onClick = { () => {console.log('我是最外層的組件A')} }>我是最外層的組件A
                <ChildB />
            </div>
        )
    }

    // 當組件B組織了事件冒泡以後, 事件A的事件將不再被運行
    function ChildB(props) {
        return (
            <div onClick = { (e) => {console.log('我是中層的組件B'); e.stopPropagation()} }>我是最外層的組件B
            </div>
        )
    }


    export default class App extends React.PureComponent {
        render() {
            return (
                <ChildA />
            )
        }
    }
  • 通過e.nativeEvent來的到真實的事件e對象
  • 如果我們將react混合其他的一些庫一起用, 在其他庫中也有可能利用事件委託給document註冊事件導致跟react衝突的話, 可以使用e.native.stopImmediatePropagation()來阻止剩餘的其他事件運行
  • 爲了提高執行效率, react使用了事件對象池來處理事件對象, 也就是說在兩個事件處理函數中我們得到的e可能是同一個,(所謂同一個也就是地址是一個, 但是react會把這個對象中的屬性的值清空: 類似於type = null這種操作, 在下一次事件觸發的時候給屬性重新賦值)
// 我們定義一個prev來保存子組件B中的事件對象e, 然後拿到父組件a中的事件對象e跟prev進行比對
// 我們會發現prev === e 會返回true, 這就是react的事件池
let prev = null;
// 組件A
function ChildA(props) {
    return (
        <div onClick = { (e) => {
                console.log('我是最外層的組件A')
                console.log(prev, e, e === prev);
                } }>我是父組件A
            <ChildB />
        </div>
    )
}

// 組件B
function ChildB(props) {
    return (
        <div onClick = { (e) => {
            console.log('我是中層的組件B'); 
            console.log(e); 
            prev = e;
        
        } }>
            我是子組件B
        </div>
    )
}


export default class App extends React.PureComponent {
    render() {
        return (
            <ChildA />
        )
    }
}

通過上面的例子, 所以我們一定要注意在事件處理程序中不要異步的使用事件對象e,如果我們一定想要異步使用, 那麼要在異步使用之前調用e.persist來進行持久化

// 如下操作, 可以被允許異步使用事件對象e
//...類組件...
render() {
    return (
        <button onClick = { (e) => {
            e.presist();
            setTimeout(() => {
                console.log(e.type);
            }, 1000)
        } }></button>
    )
}
//...

瞭解一些react事件原理方面的知識以後, 可以少踩很多坑避免很多不必要的麻煩(都是個人理解, 如果有覺得有問題的地方希望指出)

發佈了28 篇原創文章 · 獲贊 11 · 訪問量 2050
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章