<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【React】786- 探索 React 合成事件

          共 8666字,需瀏覽 18分鐘

           ·

          2020-11-25 06:02

          React 是一個(gè) Facebook 開(kāi)源的,用于構(gòu)建用戶(hù)界面的 JavaScript 庫(kù)。

          React 目的在于解決:構(gòu)建隨著時(shí)間數(shù)據(jù)不斷變化的大規(guī)模應(yīng)用程序。其中 React 合成事件是較為重要的知識(shí)點(diǎn),閱讀完本文,你將收獲:

          1. 合成事件的概念和作用;
          2. 合成事件與原生事件的 3 個(gè)區(qū)別;
          3. 合成事件與原生事件的執(zhí)行順序;
          4. 合成事件的事件池;
          5. 合成事件 4 個(gè)常見(jiàn)問(wèn)題。

          接下來(lái)和我一起開(kāi)始學(xué)習(xí)吧~

          一、概念介紹

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

          const?button?=?<button?onClick={handleClick}>Leo?按鈕button>

          在 React 中,所有事件都是合成的,不是原生 DOM 事件,但可以通過(guò) e.nativeEvent?屬性獲取 DOM 事件。

          const?handleClick?=?(e)?=>?console.log(e.nativeEvent);;
          const?button?=?<button?onClick={handleClick}>Leo?按鈕button>

          學(xué)習(xí)一個(gè)新知識(shí)的時(shí)候,一定要知道為什么會(huì)出現(xiàn)這個(gè)技術(shù)。那么 React 為什么使用合成事件?其主要有三個(gè)目的:

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

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

          1. 避免垃圾回收

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

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

          本文不介紹源碼啦,對(duì)具體實(shí)現(xiàn)的源碼有興趣的朋友可以查閱:《React SyntheticEvent》 。

          二、原生事件回顧

          在開(kāi)始介紹 React 合成事件之前,我們先簡(jiǎn)單回顧 JavaScript 原生事件中幾個(gè)重要知識(shí)點(diǎn):

          1. 事件捕獲

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

          2. 事件目標(biāo)

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

          3. 事件冒泡

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

          4. 事件委托/事件代理

          簡(jiǎn)單理解就是將一個(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)綁定事件

          二、合成事件與原生事件區(qū)別

          React 事件與原生事件很相似,但不完全相同。這里列舉幾個(gè)常見(jiàn)區(qū)別:

          1. 事件名稱(chēng)命名方式不同

          原生事件命名為純小寫(xiě)(onclick, onblur),而 React 事件命名采用小駝峰式(camelCase),如 onClick 等:

          //?原生事件綁定方式
          "handleClick()">Leo?按鈕命名</button>
          ??????
          /
          /?React?合成事件綁定方式
          const?button?=?Leo?按鈕命名button>

          2. 事件處理函數(shù)寫(xiě)法不同

          原生事件中事件處理函數(shù)為字符串,在 React JSX 語(yǔ)法中,傳入一個(gè)函數(shù)作為事件處理函數(shù)。

          //?原生事件?事件處理函數(shù)寫(xiě)法
          "handleClick()">Leo?按鈕命名</button>
          ??????
          /
          /?React?合成事件?事件處理函數(shù)寫(xiě)法
          const?button?=?Leo?按鈕命名button>

          3. 阻止默認(rèn)行為方式不同

          在原生事件中,可以通過(guò)返回 false?方式來(lái)阻止默認(rèn)行為,但是在 React 中,需要顯式使用 preventDefault()?方法來(lái)阻止。這里以阻止 ?標(biāo)簽?zāi)J(rèn)打開(kāi)新頁(yè)面為例,介紹兩種事件區(qū)別:

          //?原生事件阻止默認(rèn)行為方式
          "https://www.pingan8787.com"?
          ??onclick="console.log('Leo?阻止原生事件~');?return?false"
          >
          ??Leo?阻止原生事件
          </a>

          /
          /?React?事件阻止默認(rèn)行為方式
          const?handleClick?=?e?=>?{
          ??e.preventDefault();
          ??console.log('Leo?阻止原生事件~');
          }
          const?clickElement?=?/www.pingan8787.com"?onClick={handleClick}>
          ??Leo?阻止原生事件
          a>

          4. 小結(jié)

          小結(jié)前面幾點(diǎn)區(qū)別:


          原生事件React 事件
          事件名稱(chēng)命名方式名稱(chēng)全部小寫(xiě)
          (onclick, onblur)
          名稱(chēng)采用小駝峰
          (onClick, onBlur)
          事件處理函數(shù)語(yǔ)法字符串函數(shù)
          阻止默認(rèn)行為方式事件返回?false使用?e.preventDefault()?方法
          Native-Event-VS-Synthetic-Event.png

          三、React 事件與原生事件執(zhí)行順序

          在 React 中,“合成事件”會(huì)以事件委托(Event Delegation)方式綁定在組件最上層,并在組件卸載(unmount)階段自動(dòng)銷(xiāo)毀綁定的事件。這里我們手寫(xiě)一個(gè)簡(jiǎn)單示例來(lái)觀察 React 事件和原生事件的執(zhí)行順序:

          class?App?extends?React.Component<any,?any>?{
          ??parentRef:?any;
          ??childRef:?any;
          ??constructor(props:?any)?{
          ????super(props);
          ????this.parentRef?=?React.createRef();
          ????this.childRef?=?React.createRef();
          ??}
          ??componentDidMount()?{
          ????console.log("React componentDidMount!");
          ????this.parentRef.current?.addEventListener("click",?()?=>?{
          ??????console.log("原生事件:父元素 DOM 事件監(jiān)聽(tīng)!");
          ????});
          ????this.childRef.current?.addEventListener("click",?()?=>?{
          ??????console.log("原生事件:子元素 DOM 事件監(jiān)聽(tīng)!");
          ????});
          ????document.addEventListener("click",?(e)?=>?{
          ??????console.log("原生事件:document DOM 事件監(jiān)聽(tīng)!");
          ????});
          ??}
          ??parentClickFun?=?()?=>?{
          ????console.log("React 事件:父元素事件監(jiān)聽(tīng)!");
          ??};
          ??childClickFun?=?()?=>?{
          ????console.log("React 事件:子元素事件監(jiān)聽(tīng)!");
          ??};
          ??render()?{
          ????return?(
          ??????<div?ref={this.parentRef}?onClick={this.parentClickFun}>
          ????????<div?ref={this.childRef}?onClick={this.childClickFun}>
          ??????????分析事件執(zhí)行順序
          ????????div>

          ??????div>
          ????);
          ??}
          }
          export?default?App;

          觸發(fā)事件后,可以看到控制臺(tái)輸出:

          原生事件:子元素 DOM 事件監(jiān)聽(tīng)!?
          原生事件:父元素 DOM 事件監(jiān)聽(tīng)!?
          React 事件:子元素事件監(jiān)聽(tīng)!?
          React 事件:父元素事件監(jiān)聽(tīng)!?
          原生事件:document?DOM 事件監(jiān)聽(tīng)!?

          通過(guò)上面流程,我們可以理解:


          大致流程如下如:
          Native-Event-And-Synthetic-Event.png

          四、合成事件的事件池**

          1. 事件池介紹

          合成事件對(duì)象池,是 React?事件系統(tǒng)提供的一種性能優(yōu)化方式合成事件對(duì)象在事件池統(tǒng)一管理不同類(lèi)型的合成事件具有不同的事件池

          關(guān)于“事件池是如何工作”的問(wèn)題,可以看看下面圖片:

          Synthetic-Event-Loop.png

          (圖片來(lái)自:ReactDeveloper https://juejin.cn/post/6844903862285893639)

          2. 事件池分析(React 16 版本)

          React 事件池僅支持在 React 16 及更早版本中,在 React 17 已經(jīng)不使用事件池。下面以 React 16 版本為例:

          function?handleChange(e)?{
          ??console.log("原始數(shù)據(jù):",?e.target)
          ??setTimeout(()?=>?{
          ????console.log("定時(shí)任務(wù) e.target:",?e.target);?//?null
          ????console.log("定時(shí)任務(wù):e:",?e);?
          ??},?100);
          }
          function?App()?{
          ??return?(
          ????<div?className="App">
          ??????<button?onClick={handleChange}>測(cè)試事件池button>

          ????div>
          ??);
          }

          export?default?App;

          可以看到輸出:

          在 React 16 及之前的版本,合成事件對(duì)象的事件處理函數(shù)全部被調(diào)用之后,所有屬性都會(huì)被置為 null?。這時(shí),如果我們需要在事件處理函數(shù)運(yùn)行之后獲取事件對(duì)象的屬性,可以使用 React 提供的 e.persist()?方法,保留所有屬性:

          //?只修改?handleChange?方法,其他不變
          function?handleChange(e)?{
          ??//?只增加?persist()?執(zhí)行
          ??e.persist();
          ??
          ??console.log("原始數(shù)據(jù):",?e.target)
          ??setTimeout(()?=>?{
          ????console.log("定時(shí)任務(wù) e.target:",?e.target);?//?null
          ????console.log("定時(shí)任務(wù):e:",?e);?
          ??},?100);
          }

          再看下結(jié)果:

          Synthetic-Event-React17.png

          3. 事件池分析(React 17 版本)

          由于 Web 端的 React 17 不使用事件池,所有不會(huì)存在上述“所有屬性都會(huì)被置為 null”的問(wèn)題。

          五、常見(jiàn)問(wèn)題

          1. React 事件中 this 指向問(wèn)題

          在 React 中,JSX 回調(diào)函數(shù)中的 this 經(jīng)常會(huì)出問(wèn)題,在 Class 中方法不會(huì)默認(rèn)綁定 this,就會(huì)出現(xiàn)下面情況, this.funName?值為 undefined?:

          class?App?extends?React.Component<any,?any>?{
          ??childClickFun?=?()?=>?{
          ????console.log("React?事件");
          ??};
          ??clickFun()?{
          ????console.log("React?this?指向問(wèn)題",?this.childClickFun);?//?undefined
          ??}
          ??render()?{
          ????return?(
          ????????<div?onClick={this.clickFun}>React?this?指向問(wèn)題div>
          ????);
          ??}
          }
          export?default?App;

          我們有 2 種方式解決這個(gè)問(wèn)題:

          1. 使用 bind?方法綁定 this?:
          class?App?extends?React.Component<any,?any>?{
          ??constructor(props:?any)?{
          ????super(props);
          ????this.clickFun?=?this.clickFun.bind(this);
          ??}
          ??
          ??//?省略其他代碼
          }
          export?default?App;
          1. 將需要使用 this 的方法改寫(xiě)為使用箭頭函數(shù)定義:
          class?App?extends?React.Component<any,?any>?{
          ??clickFun?=?()?=>?{
          ????console.log("React?this?指向問(wèn)題",?this.childClickFun);?//?undefined
          ??}
          ??
          ??//?省略其他代碼
          }
          export?default?App;

          或者在回調(diào)函數(shù)中使用箭頭函數(shù)

          class?App?extends?React.Component<any,?any>?{
          ??//?省略其他代碼
          ??clickFun()?{
          ????console.log("React?this?指向問(wèn)題",?this.childClickFun);?//?undefined
          ??}
          ??render()?{
          ????return?(
          ????????<div?onClick={()?=>?this.clickFun()}>React?this?指向問(wèn)題div>
          ????);
          ??}
          }
          export?default?App;

          2. 向事件傳遞參數(shù)問(wèn)題

          經(jīng)常在遍歷列表時(shí),需要向事件傳遞額外參數(shù),如 id?等,來(lái)指定需要操作的數(shù)據(jù),在 React 中,可以使用 2 種方式向事件傳參:

          const?List?=?[1,2,3,4];
          class?App?extends?React.Component<any,?any>?{
          ??//?省略其他代碼
          ??clickFun?(id)?{console.log('當(dāng)前點(diǎn)擊:',?id)}
          ??render()?{
          ????return?(
          ????????<div>
          ?????????<h1>第一種:通過(guò) bind 綁定 this 傳參h1>

          ?????????{
          ???????????List.map(item?=>?<div?onClick={this.clickFun.bind(this,?item)}>按鈕:{item}div>)
          ??????????}
          ?????????<h1>第二種:通過(guò)箭頭函數(shù)綁定 this 傳參h1>
          ?????????{
          ???????????List.map(item?=>?<div?onClick={()?=>?this.clickFun(item)}>按鈕:{item}div>)
          ??????????}
          ????????div>
          ????);
          ??}
          }
          export?default?App;

          這兩種方式是等價(jià)的:

          3. 合成事件阻止冒泡

          官網(wǎng)文檔描述了:

          從 v0.14 開(kāi)始,事件處理器返回 false 時(shí),不再阻止事件傳遞。你可以酌情手動(dòng)調(diào)用 e.stopPropagation() 或 e.preventDefault() 作為替代方案。

          也就是說(shuō),在 React 合成事件中,需要阻止冒泡時(shí),可以使用 e.stopPropagation()e.preventDefault() ?方法來(lái)解決,另外還可以使用 e.nativeEvent.stopImmediatePropagation() 方法解決。

          3.1 e.stopPropagation

          對(duì)于開(kāi)發(fā)者來(lái)說(shuō),更希望使用 e.stopPropagation() 方法來(lái)阻止當(dāng)前 DOM 事件冒泡,但事實(shí)上,從前兩節(jié)介紹的執(zhí)行順序可知,e.stopPropagation() 只能阻止合成事件間冒泡,即下層的合成事件,不會(huì)冒泡到上層的合成事件。事件本身還都是在 document 上執(zhí)行。所以最多只能阻止 document 事件不能再冒泡到 window 上。

          class?App?extends?React.Component<any,?any>?{
          ??parentRef:?any;
          ??childRef:?any;
          ??constructor(props:?any)?{
          ????super(props);
          ????this.parentRef?=?React.createRef();
          ??}
          ??componentDidMount()?{
          ????this.parentRef.current?.addEventListener("click",?()?=>?{
          ??????console.log("阻止原生事件冒泡~");
          ????});
          ????document.addEventListener("click",?(e)?=>?{
          ??????console.log("原生事件:document DOM 事件監(jiān)聽(tīng)!");
          ????});
          ??}
          ??parentClickFun?=?(e:?any)?=>?{
          ????e.stopPropagation();
          ????console.log("阻止合成事件冒泡~");
          ??};
          ??render()?{
          ????return?(
          ??????this.parentRef}?onClick={this.parentClickFun}>
          ????????點(diǎn)擊測(cè)試“合成事件和原生事件是否可以混用”
          ??????</div>
          ????);
          ??}
          }
          export?default?App;

          輸出結(jié)果:

          阻止原生事件冒泡~?
          阻止合成事件冒泡~?

          3.2 e.nativeEvent.stopImmediatePropagation

          該方法可以阻止監(jiān)聽(tīng)同一事件的其他事件監(jiān)聽(tīng)器被調(diào)用。在 React 中,一個(gè)組件只能綁定一個(gè)同類(lèi)型的事件監(jiān)聽(tīng)器,當(dāng)重復(fù)定義時(shí),后面的監(jiān)聽(tīng)器會(huì)覆蓋之前的。事實(shí)上 nativeEvent 的 stopImmediatePropagation只能阻止綁定在 document 上的事件監(jiān)聽(tīng)器。而合成事件上的 e.nativeEvent.stopImmediatePropagation() ?能阻止合成事件不會(huì)冒泡到 document 上

          舉一個(gè)實(shí)際案例:實(shí)現(xiàn)點(diǎn)擊空白處關(guān)閉菜單的功能:當(dāng)菜單打開(kāi)時(shí),在 document 上動(dòng)態(tài)注冊(cè)事件,用來(lái)關(guān)閉菜單。

          在菜單關(guān)閉的一刻,在 document 上移除該事件,這樣就不會(huì)重復(fù)執(zhí)行該事件,浪費(fèi)性能,也可以在 window 上注冊(cè)事件,這樣可以避開(kāi) document。**

          4. 合成事件和原生事件是否可以混用

          合成事件和原生事件最好不要混用。原生事件中如果執(zhí)行了stopPropagation方法,則會(huì)導(dǎo)致其他React事件失效。因?yàn)樗性氐氖录o(wú)法冒泡到document上。通過(guò)前面介紹的兩者事件執(zhí)行順序來(lái)看,所有的 React 事件都將無(wú)法被注冊(cè)。通過(guò)代碼一起看看:

          class?App?extends?React.Component<any,?any>?{
          ??parentRef:?any;
          ??childRef:?any;
          ??constructor(props:?any)?{
          ????super(props);
          ????this.parentRef?=?React.createRef();
          ??}
          ??componentDidMount()?{
          ????this.parentRef.current?.addEventListener("click",?(e:?any)?=>?{
          ?????e.stopPropagation();
          ??????console.log("阻止原生事件冒泡~");
          ????});
          ????document.addEventListener("click",?(e)?=>?{
          ??????console.log("原生事件:document DOM 事件監(jiān)聽(tīng)!");
          ????});
          ??}
          ??parentClickFun?=?(e:?any)?=>?{
          ????console.log("阻止合成事件冒泡~");
          ??};
          ??render()?{
          ????return?(
          ??????this.parentRef}?onClick={this.parentClickFun}>
          ????????點(diǎn)擊測(cè)試“合成事件和原生事件是否可以混用”
          ??????</div>
          ????);
          ??}
          }
          export?default?App;

          輸出結(jié)果:

          阻止原生事件冒泡~?

          好了,本文就寫(xiě)到這里,建議大家可以再回去看下官方文檔《合成事件》《事件處理》章節(jié)理解,有興趣的朋友也可以閱讀源碼《React SyntheticEvent.js》。

          總結(jié)

          最后在回顧下本文學(xué)習(xí)目標(biāo):

          1. 合成事件的概念和作用;
          2. 合成事件與原生事件的 3 個(gè)區(qū)別;
          3. 合成事件與原生事件的執(zhí)行順序;
          4. 合成事件的事件池;
          5. 合成事件 4 個(gè)常見(jiàn)問(wèn)題。

          你是否都清楚了?歡迎一起討論學(xué)習(xí)。

          參考文章

          1.《事件處理與合成事件(react)》
          2.官方文檔《合成事件》《事件處理》
          3.《React合成事件和DOM原生事件混用須知》
          4.《React 合成事件系統(tǒng)之事件池》

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(mén)(上)||?Webpack4 入門(mén)(下)
          6.?MobX 入門(mén)(上)?||??MobX 入門(mén)(下)
          7. 80+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 80+ 篇原創(chuàng)文章


          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品国产乱码一区二区 | 国产视频1区 | 好吊视频一区二区三区四区五区六区七区八区 | 亚洲成人大片 | 亚洲成肉网 |