【React深刻】React事件機制

關於React事件的疑問

  • 1.爲何要手動綁定this
  • 2.React事件和原生事件有什麼區別
  • 3.React事件和原生事件的執行順序,能夠混用嗎
  • 4.React事件如何解決跨瀏覽器兼容
  • 5.什麼是合成事件

下面是我閱讀過源碼後,將全部的執行流程總結出來的流程圖,不會貼代碼,若是你想閱讀代碼看看具體是如何實現的,能夠根據流程圖去源碼裏尋找。node

事件註冊

image

  • 組件裝載 / 更新。
  • 經過lastPropsnextProps判斷是否新增、刪除事件分別調用事件註冊、卸載方法。
  • 調用EventPluginHubenqueuePutListener進行事件存儲
  • 獲取document對象。
  • 根據事件名稱(如onClickonCaptureClick)判斷是進行冒泡仍是捕獲。
  • 判斷是否存在addEventListener方法,不然使用attachEvent(兼容IE)。
  • document註冊原生事件回調爲dispatchEvent(統一的事件分發機制)。

事件存儲

image

  • EventPluginHub負責管理React合成事件的callback,它將callback存儲在listenerBank中,另外還存儲了負責合成事件的Plugin
  • EventPluginHubputListener方法是向存儲容器中增長一個listener。
  • 獲取綁定事件的元素的惟一標識key
  • callback根據事件類型,元素的惟一標識key存儲在listenerBank中。
  • listenerBank的結構是:listenerBank[registrationName][key]

例如:react

{
    onClick:{
        nodeid1:()=>{...}
        nodeid2:()=>{...}
    },
    onChange:{
        nodeid3:()=>{...}
        nodeid4:()=>{...}
    }
}

事件觸發 / 執行

image

這裏的事件執行利用了React的批處理機制,在前一篇的【React深刻】setState執行機制中已經分析過,這裏再也不多加分析。segmentfault

  • 觸發document註冊原生事件的回調dispatchEvent
  • 獲取到觸發這個事件最深一級的元素

例以下面的代碼:首先會獲取到this.child瀏覽器

<div onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div onClick={this.childClick} ref={ref => this.child = ref}>
          test
        </div>
      </div>
  • 遍歷這個元素的全部父元素,依次對每一級元素進行處理。
  • 構造合成事件。
  • 將每一級的合成事件存儲在eventQueue事件隊列中。
  • 遍歷eventQueue
  • 經過isPropagationStopped判斷當前事件是否執行了阻止冒泡方法。
  • 若是阻止了冒泡,中止遍歷,不然經過executeDispatch執行合成事件。
  • 釋放處理完成的事件。

react在本身的合成事件中重寫了stopPropagation方法,將isPropagationStopped設置爲true,而後在遍歷每一級事件的過程當中根據此遍歷判斷是否繼續執行。這就是react本身實現的冒泡機制。babel

合成事件

image

  • 調用EventPluginHubextractEvents方法。
  • 循環全部類型的EventPlugin(用來處理不一樣事件的工具方法)。
  • 在每一個EventPlugin中根據不一樣的事件類型,返回不一樣的事件池。
  • 在事件池中取出合成事件,若是事件池是空的,那麼建立一個新的。
  • 根據元素nodeid(惟一標識key)和事件類型從listenerBink中取出回調函數
  • 返回帶有合成事件參數的回調函數

總流程

將上面的四個流程串聯起來。dom

image

爲何要手動綁定this

經過事件觸發過程的分析,dispatchEvent調用了invokeGuardedCallback方法。函數

function invokeGuardedCallback(name, func, a) {
  try {
    func(a);
  } catch (x) {
    if (caughtError === null) {
      caughtError = x;
    }
  }
}

可見,回調函數是直接調用調用的,並無指定調用的組件,因此不進行手動綁定的狀況下直接獲取到的thisundefined工具

這裏可使用實驗性的屬性初始化語法 ,也就是直接在組件聲明箭頭函數。箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this。所以這樣咱們在React事件中獲取到的就是組件自己了。this

和原生事件有什麼區別

  • React 事件使用駝峯命名,而不是所有小寫。
  • 經過 JSX , 你傳遞一個函數做爲事件處理程序,而不是一個字符串。

例如,HTMLspa

<button onclick="activateLasers()">
  Activate Lasers
</button>

React 中略有不一樣:

<button onClick={activateLasers}>
  Activate Lasers
</button>

另外一個區別是,在 React 中你不能經過返回 false 來阻止默認行爲。必須明確調用 preventDefault

由上面執行機制咱們能夠得出:React本身實現了一套事件機制,本身模擬了事件冒泡和捕獲的過程,採用了事件代理,批量更新等方法,而且抹平了各個瀏覽器的兼容性問題。

React事件和原生事件的執行順序

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>)
  }

執行結果:

image

由上面的流程咱們能夠理解:

  • react的全部事件都掛載在document
  • 當真實dom觸發後冒泡到document後纔會對react事件進行處理
  • 因此原生的事件會先執行
  • 而後執行react合成事件
  • 最後執行真正在document上掛載的事件

react事件和原生事件能夠混用嗎?

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

原生事件中若是執行了stopPropagation方法,則會致使其餘react事件失效。由於全部元素的事件將沒法冒泡到document上。

由上面的執行機制不可貴出,全部的react事件都將沒法被註冊。

合成事件、瀏覽器兼容

function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
這裏, e 是一個合成的事件。 React 根據 W3C 規範 定義了這個合成事件,因此你不須要擔憂跨瀏覽器的兼容性問題。

事件處理程序將傳遞 SyntheticEvent 的實例,這是一個跨瀏覽器原生事件包裝器。 它具備與瀏覽器原生事件相同的接口,包括 stopPropagation()preventDefault() ,在全部瀏覽器中他們工做方式都相同。

每一個 SyntheticEvent 對象都具備如下屬性:

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

React合成的SyntheticEvent採用了事件池,這樣作能夠大大節省內存,而不會頻繁的建立和銷燬事件對象。

另外,無論在什麼瀏覽器環境下,瀏覽器會將該事件類型統一建立爲合成事件,從而達到了瀏覽器兼容的目的。

推薦閱讀

【React深刻】setState的執行機制

相關文章
相關標籤/搜索