溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

react要用合成事件的原因是什么

發(fā)布時(shí)間:2022-07-15 09:41:56 來源:億速云 閱讀:175 作者:iii 欄目:web開發(fā)

本文小編為大家詳細(xì)介紹“react要用合成事件的原因是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“react要用合成事件的原因是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

react使用合成事件主要有三個(gè)目的:1、進(jìn)行瀏覽器兼容,實(shí)現(xiàn)更好的跨平臺(tái);React提供的合成事件可用來抹平不同瀏覽器事件對(duì)象之間的差異,將不同平臺(tái)事件模擬合成事件。2、避免垃圾回收;React事件對(duì)象不會(huì)被釋放掉,而是存放進(jìn)一個(gè)數(shù)組中,當(dāng)事件觸發(fā),就從這個(gè)數(shù)組中彈出,避免頻繁地去創(chuàng)建和銷毀(垃圾回收)。3、方便事件統(tǒng)一管理和事務(wù)機(jī)制。

react要用合成事件的原因是什么

本教程操作環(huán)境:Windows7系統(tǒng)、react18版、Dell G3電腦。

一、什么是合成事件

React 合成事件(SyntheticEvent)是 React 模擬原生 DOM 事件所有能力的一個(gè)事件對(duì)象,即瀏覽器原生事件的跨瀏覽器包裝器。它根據(jù) W3C 規(guī)范 來定義合成事件,兼容所有瀏覽器,擁有與瀏覽器原生事件相同的接口。

在 React 中,所有事件都是合成的,不是原生 DOM 事件,但可以通過 e.nativeEvent 屬性獲取 DOM 事件。 比如:

const button = <button onClick={handleClick}>react 按鈕</button>
const handleClick = (e) => console.log(e.nativeEvent); //原生事件對(duì)象

學(xué)習(xí)一個(gè)新知識(shí)的時(shí)候,一定要知道為什么會(huì)出現(xiàn)這個(gè)技術(shù)。

那么 React 為什么使用合成事件?其主要有三個(gè)目的:

  • 進(jìn)行瀏覽器兼容,實(shí)現(xiàn)更好的跨平臺(tái)

    React 采用的是頂層事件代理機(jī)制,能夠保證冒泡一致性,可以跨瀏覽器執(zhí)行。React 提供的合成事件用來抹平不同瀏覽器事件對(duì)象之間的差異,將不同平臺(tái)事件模擬合成事件。

  • 避免垃圾回收

    事件對(duì)象可能會(huì)被頻繁創(chuàng)建和回收,因此 React 引入事件池,在事件池中獲取或釋放事件對(duì)象。即 React 事件對(duì)象不會(huì)被釋放掉,而是存放進(jìn)一個(gè)數(shù)組中,當(dāng)事件觸發(fā),就從這個(gè)數(shù)組中彈出,避免頻繁地去創(chuàng)建和銷毀(垃圾回收)。

  • 方便事件統(tǒng)一管理和事務(wù)機(jī)制

本文不介紹源碼啦,對(duì)具體實(shí)現(xiàn)的源碼有興趣的朋友可以查閱:《React SyntheticEvent》 。
https://github.com/facebook/react/blob/75ab53b9e1de662121e68dabb010655943d28d11/packages/events/SyntheticEvent.js#L62

二、原生事件回顧

JavaScript事件模型主要分為3種:原始事件模型(DOM0)、DOM2事件模型、IE事件模型。

1.DOM0事件模型

又稱為原始事件模型,在該模型中,事件不會(huì)傳播,即沒有事件流的概念。事件綁定監(jiān)聽函數(shù)比較簡單, 有兩種方式:

//HTML代碼種直接綁定:
<button type='button' id="test" onclick="fun()"/>

//通過JS代碼指定屬性值:
var btn = document.getElementById('.test');
btn.onclick = fun;
//移除監(jiān)聽函數(shù):
btn.onclick = null;

優(yōu)點(diǎn):兼容性強(qiáng) 支持所有瀏覽器

缺點(diǎn): 邏輯與顯示沒有分離;相同事件的監(jiān)聽函數(shù)只能綁定一個(gè),后邊注冊(cè)的同種事件會(huì)覆蓋之前注冊(cè)的。

2.DOM2事件模型

W3C制定的標(biāo)準(zhǔn)模型,現(xiàn)代瀏覽器(除IE6-8之外的瀏覽器)都支持該模型。在該事件模型中,一次事件共有三個(gè)過程:

事件捕獲階段(capturing phase)。事件從document一直向下傳播到目標(biāo)元素, 依次檢查經(jīng)過的節(jié)點(diǎn)是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。

事件處理階段(target phase)。事件到達(dá)目標(biāo)元素, 觸發(fā)目標(biāo)元素的監(jiān)聽函數(shù)。

事件冒泡階段(bubbling phase)。事件從目標(biāo)元素冒泡到document, 依次檢查經(jīng)過的節(jié)點(diǎn)是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。

//事件綁定監(jiān)聽函數(shù)的方式如下:
addEventListener(eventType, handler, useCapture)

//事件移除監(jiān)聽函數(shù)的方式如下:
removeEventListener(eventType, handler, useCapture)

3.IE事件模型

IE事件模型共有兩個(gè)過程:

事件處理階段(target phase)。事件到達(dá)目標(biāo)元素, 觸發(fā)目標(biāo)元素的監(jiān)聽函數(shù)。

事件冒泡階段(bubbling phase)。事件從目標(biāo)元素冒泡到document, 依次檢查經(jīng)過的節(jié)點(diǎn)是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。

//事件綁定監(jiān)聽函數(shù)的方式如下:
attachEvent(eventType, handler)

//事件移除監(jiān)聽函數(shù)的方式如下:
detachEvent(eventType, handler)

4.事件流

react要用合成事件的原因是什么

如上圖所示,所謂事件流包括三個(gè)階段:事件捕獲、目標(biāo)階段和事件冒泡。事件捕獲是從外到里,對(duì)應(yīng)圖中的紅色箭頭標(biāo)注部分window -> document -> html … -> target,目標(biāo)階段是事件真正發(fā)生并處理的階段,事件冒泡是從里到外,對(duì)應(yīng)圖中的target -> … -> html -> document -> window。

  • 事件捕獲

    當(dāng)某個(gè)元素觸發(fā)某個(gè)事件(如 onclick ),頂層對(duì)象 document 就會(huì)發(fā)出一個(gè)事件流,隨著 DOM 樹的節(jié)點(diǎn)向目標(biāo)元素節(jié)點(diǎn)流去,直到到達(dá)事件真正發(fā)生的目標(biāo)元素。在這個(gè)過程中,事件相應(yīng)的監(jiān)聽函數(shù)是不會(huì)被觸發(fā)的。

  • 事件目標(biāo)

    當(dāng)?shù)竭_(dá)目標(biāo)元素之后,執(zhí)行目標(biāo)元素該事件相應(yīng)的處理函數(shù)。如果沒有綁定監(jiān)聽函數(shù),那就不執(zhí)行。

  • 事件冒泡

    從目標(biāo)元素開始,往頂層元素傳播。途中如果有節(jié)點(diǎn)綁定了相應(yīng)的事件處理函數(shù),這些函數(shù)都會(huì)被觸發(fā)一次。如果想阻止事件起泡,可以使用 e.stopPropagation() 或者 e.cancelBubble=true(IE)來阻止事件的冒泡傳播。

  • 事件委托/事件代理

    簡單理解就是將一個(gè)響應(yīng)事件委托到另一個(gè)元素。 當(dāng)子節(jié)點(diǎn)被點(diǎn)擊時(shí),click 事件向上冒泡,父節(jié)點(diǎn)捕獲到事件后,我們判斷是否為所需的節(jié)點(diǎn),然后進(jìn)行處理。其優(yōu)點(diǎn)在于減少內(nèi)存消耗和動(dòng)態(tài)綁定事件。

三、React合成事件原理

React合成事件的工作原理大致可以分為兩個(gè)階段:

  • 事件綁定

  • 事件觸發(fā)

在React17之前,React是把事件委托在document上的,React17及以后版本不再把事件委托在document上,而是委托在掛載的容器上了,本文以16.x版本的React為例來探尋React的合成事件。當(dāng)真實(shí)的dom觸發(fā)事件時(shí),此時(shí)構(gòu)造React合成事件對(duì)象,按照冒泡或者捕獲的路徑去收集真正的事件處理函數(shù),在此過程中會(huì)先處理原生事件,然后當(dāng)冒泡到document對(duì)象后,再處理React事件。舉個(gè)栗子:

import React from 'react';
import './App.less';

class Test extends React.Component {
  parentRef: React.RefObject<any>;

  childRef: React.RefObject<any>;

  constructor(props) {
    super(props);
    this.parentRef = React.createRef();
    this.childRef = React.createRef();
  }

  componentDidMount() {
    document.addEventListener(
      'click',
      () => {
        console.log(`document原生事件捕獲`);
      },
      true,
    );
    document.addEventListener('click', () => {
      console.log(`document原生事件冒泡`);
    });
    this.parentRef.current.addEventListener(
      'click',
      () => {
        console.log(`父元素原生事件捕獲`);
      },
      true,
    );
    this.parentRef.current.addEventListener('click', () => {
      console.log(`父元素原生事件冒泡`);
    });
    this.childRef.current.addEventListener(
      'click',
      () => {
        console.log(`子元素原生事件捕獲`);
      },
      true,
    );
    this.childRef.current.addEventListener('click', () => {
      console.log(`子元素原生事件冒泡`);
    });
  }

  handleParentBubble = () => {
    console.log(`父元素React事件冒泡`);
  };

  handleChildBubble = () => {
    console.log(`子元素React事件冒泡`);
  };

  handleParentCapture = () => {
    console.log(`父元素React事件捕獲`);
  };

  handleChileCapture = () => {
    console.log(`子元素React事件捕獲`);
  };

  render() {
    return (
      <p
        ref={this.parentRef}
        onClick={this.handleParentBubble}
        onClickCapture={this.handleParentCapture}
      >
        <p
          ref={this.childRef}
          onClick={this.handleChildBubble}
          onClickCapture={this.handleChileCapture}
        >
          事件處理測(cè)試
        </p>
      </p>
    );
  }
}

export default Test;

上面案例打印的結(jié)果為:

react要用合成事件的原因是什么

注:React17中上述案例的執(zhí)行會(huì)有所區(qū)別,會(huì)先執(zhí)行所有捕獲事件后,再執(zhí)行所有冒泡事件。

1、事件綁定

通過上述案例,我們知道了React合成事件和原生事件執(zhí)行的過程,兩者其實(shí)是通過一個(gè)叫事件插件(EventPlugin)的模塊產(chǎn)生關(guān)聯(lián)的,每個(gè)插件只處理對(duì)應(yīng)的合成事件,比如onClick事件對(duì)應(yīng)SimpleEventPlugin插件,這樣React在一開始會(huì)把這些插件加載進(jìn)來,通過插件初始化一些全局對(duì)象,比如其中有一個(gè)對(duì)象是registrationNameDependencies,它定義了合成事件與原生事件的對(duì)應(yīng)關(guān)系如下:

{
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    ...
}

registrationNameModule對(duì)象指定了React事件到對(duì)應(yīng)插件plugin的映射:

{
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    ...
}

plugins對(duì)象就是上述插件的列表。在某個(gè)節(jié)點(diǎn)渲染過程中,合成事件比如onClick是作為它的prop的,如果判斷該prop為事件類型,根據(jù)合成事件類型找到對(duì)應(yīng)依賴的原生事件注冊(cè)綁定到頂層document上,dispatchEvent為統(tǒng)一的事件處理函數(shù)。

2、事件觸發(fā)

當(dāng)任意事件觸發(fā)都會(huì)執(zhí)行dispatchEvent函數(shù),比如上述事例中,當(dāng)用戶點(diǎn)擊Child的p時(shí)會(huì)遍歷這個(gè)元素的所有父元素,依次對(duì)每一級(jí)元素進(jìn)行事件的收集處理,構(gòu)造合成事件對(duì)象(SyntheticEvent–也就是通常我們說的React中自定義函數(shù)的默認(rèn)參數(shù)event,原生的事件對(duì)象對(duì)應(yīng)它的一個(gè)屬性),然后由此形成了一條「鏈」,這條鏈會(huì)將合成事件依次存入eventQueue中,而后會(huì)遍歷eventQueue模擬一遍捕獲和冒泡階段,然后通過runEventsInBatch方法依次觸發(fā)調(diào)用每一項(xiàng)的監(jiān)聽事件,在此過程中會(huì)根據(jù)事件類型判斷屬于冒泡階段還是捕獲階段觸發(fā),比如onClick是在冒泡階段觸發(fā),onClickCapture是在捕獲階段觸發(fā),在事件處理完成后進(jìn)行釋放。SyntheticEvent對(duì)象屬性如下:

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent // 原生事件對(duì)象
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type

dispatchEvent偽代碼如下:

dispatchEvent = (event) => {
    const path = []; // 合成事件鏈
    let current = event.target; // 觸發(fā)事件源
    while (current) {
      path.push(current);
      current = current.parentNode; // 逐級(jí)往上進(jìn)行收集
    }
    // 模擬捕獲和冒泡階段
    // path = [target, p, body, html, ...]
    for (let i = path.length - 1; i >= 0; i--) {
      const targetHandler = path[i].onClickCapture;
      targetHandler && targetHandler();
    }
    for (let i = 0; i < path.length; i++) {
      const targetHandler = path[i].onClick;
      targetHandler && targetHandler();
    }
  };

3、更改事件委托(React v17.0)

自React發(fā)布以來, 一直自動(dòng)進(jìn)行事件委托。當(dāng) document 上觸發(fā) DOM 事件時(shí),React 會(huì)找出調(diào)用的組件,然后 React 事件會(huì)在組件中向上 “冒泡”。但實(shí)際上,原生事件已經(jīng)冒泡出了 document 級(jí)別,React 在其中安裝了事件處理器。

但是,這就是逐步升級(jí)的困難所在。

在 React 17 中,React 將不再向 document 附加事件處理器。而會(huì)將事件處理器附加到渲染 React 樹的根 DOM 容器中:

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

在 React 16 或更早版本中,React 會(huì)對(duì)大多數(shù)事件執(zhí)行 document.addEventListener()。React 17 將會(huì)在底層調(diào)用 rootNode.addEventListener()

react要用合成事件的原因是什么

四、注意

由于事件對(duì)象可能會(huì)頻繁創(chuàng)建和回收在React16.x中,合成事件SyntheticEvent采用了事件池,合成事件會(huì)被放進(jìn)事件池中統(tǒng)一管理,這樣能夠減少內(nèi)存開銷。React通過合成事件,模擬捕獲和冒泡階段,從而達(dá)到不同瀏覽器兼容的目的。另外,React不建議將原生事件和合成事件一起使用,這樣很容易造成使用混亂。

由于17版本事件委托的更改,現(xiàn)在可以更加安全地進(jìn)行新舊版本 React 樹的嵌套。請(qǐng)注意,要使其正常工作,兩個(gè)版本都必須為 17 或更高版本,這就是為什么強(qiáng)烈建議升級(jí)到 React 17 及以上的根本原因。

讀到這里,這篇“react要用合成事件的原因是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI