摘要:注冊(cè)事件的回調(diào)函數(shù)由來統(tǒng)一管理,根據(jù)事件的類型和組件標(biāo)識(shí)為唯一標(biāo)識(shí)事件并進(jìn)行存儲(chǔ)。利用中注入的例如會(huì)將原生的事件轉(zhuǎn)化成合成的事件,然后批量執(zhí)行存儲(chǔ)的回調(diào)函,回調(diào)函數(shù)的執(zhí)行分為兩步,第一步是將所有的合成事件放到事件隊(duì)列里面,第二步是逐個(gè)執(zhí)行。
最近在閱讀《深入React技術(shù)棧》一書中,發(fā)現(xiàn)了之前使用React中并沒有注意到的React事件與瀏覽器原生事件之間的區(qū)別,鑒于好久已經(jīng)沒有寫東西了,就想寫一下關(guān)于React事件的文章。
首先我們舉個(gè)例子,如果我們需要實(shí)現(xiàn)一個(gè)組件,這個(gè)組件點(diǎn)擊按鈕會(huì)顯示一個(gè)二維碼,點(diǎn)擊二維碼之外的區(qū)域可以隱藏二維碼,但是點(diǎn)擊二維碼本身卻不會(huì)關(guān)閉,代碼如下:
//代碼來源于《深入React技術(shù)?!?.1.4節(jié) class QrCode extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); this.handleClickQr = this.handleClickQr.bind(this); this.state = { active: false, }; } componentDidMount() { document.body.addEventListener("click", e => { this.setState({ active: false, }); }); } componentWillUnmount() { document.body.removeEventListener("click"); } handleClick() { this.setState({ active: !this.state.active, }); } handleClickQr(e) { e.stopPropagation(); } render() { return (); } }
上面代碼從感官上感覺確實(shí)可以實(shí)現(xiàn)要求的組件,但事實(shí)上我們運(yùn)行上述代碼可以發(fā)現(xiàn),點(diǎn)擊二維碼本身也會(huì)導(dǎo)致二維碼的隱藏,現(xiàn)在就有意思了,我們來仔細(xì)分析一下。
其實(shí)React事件并沒有原生的綁定在真實(shí)的DOM上,而是使用了行為委托方式實(shí)現(xiàn)事件機(jī)制。
如上圖所示,在JavaScript中,事件的觸發(fā)實(shí)質(zhì)上是要經(jīng)過三個(gè)階段:事件捕獲、目標(biāo)對(duì)象本身的事件處理和事件冒泡,假設(shè)在div中觸發(fā)了click事件,實(shí)際上首先經(jīng)歷捕獲階段會(huì)由父級(jí)元素將事件一直傳遞到事件發(fā)生的元素,執(zhí)行完目標(biāo)事件本身的處理事件后,然后經(jīng)歷冒泡階段,將事件從子元素向父元素冒泡。正因?yàn)槭录贒OM的傳遞經(jīng)歷這樣一個(gè)過程,從而為行為委托提供了可能。通俗地講,行為委托的實(shí)質(zhì)就是將子元素事件的處理委托給父級(jí)元素處理。React會(huì)將所有的事件都綁定在最外層(document),使用統(tǒng)一的事件監(jiān)聽,并在冒泡階段處理事件,當(dāng)掛載或者卸載組件時(shí),只需要在通過的在統(tǒng)一的事件監(jiān)聽位置增加或者刪除對(duì)象,因此可以提高效率。
并且React并沒有使用原生的瀏覽器事件,而是在基于Virtual DOM的基礎(chǔ)上實(shí)現(xiàn)了合成事件(SyntheticEvent),事件處理程序接收到的是SyntheticEvent的實(shí)例。SyntheticEvent完全符合W3C的標(biāo)準(zhǔn),因此在事件層次上具有瀏覽器兼容性,與原生的瀏覽器事件一樣擁有同樣的接口,可以通過stopPropagation()和preventDefault()相應(yīng)的中斷。如果需要訪問當(dāng)原生的事件對(duì)象,可以通過引用nativeEvent獲得。
上圖為大致的React事件機(jī)制的流程圖,React中的事件機(jī)制分為兩個(gè)階段:事件注冊(cè)和事件觸發(fā):
事件注冊(cè)
React在組件加載(mount)和更新(update)時(shí),其中的ReactDOMComponent會(huì)對(duì)傳入的事件屬性進(jìn)行處理,對(duì)相關(guān)事件進(jìn)行注冊(cè)和存儲(chǔ)。document中注冊(cè)的事件不處理具體的事件,僅對(duì)事件進(jìn)行分發(fā)。ReactBrowserEventEmitter作為事件注冊(cè)入口,擔(dān)負(fù)著事件注冊(cè)和事件觸發(fā)。注冊(cè)事件的回調(diào)函數(shù)由EventPluginHub來統(tǒng)一管理,根據(jù)事件的類型(type)和組件標(biāo)識(shí)(_rootNodeID)為key唯一標(biāo)識(shí)事件并進(jìn)行存儲(chǔ)。
事件執(zhí)行
事件執(zhí)行時(shí),document上綁定事件ReactEventListener.dispatchEvent會(huì)對(duì)事件進(jìn)行分發(fā),根據(jù)之前存儲(chǔ)的類型(type)和組件標(biāo)識(shí)(_rootNodeID)找到觸發(fā)事件的組件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPlugin、EnterLeaveEventPlugin)會(huì)將原生的DOM事件轉(zhuǎn)化成合成的事件,然后批量執(zhí)行存儲(chǔ)的回調(diào)函,回調(diào)函數(shù)的執(zhí)行分為兩步,第一步是將所有的合成事件放到事件隊(duì)列里面,第二步是逐個(gè)執(zhí)行。需要注意的是,瀏覽器原生會(huì)為每個(gè)事件的每個(gè)listener創(chuàng)建一個(gè)事件對(duì)象,可以從這個(gè)事件對(duì)象獲取到事件的引用。這會(huì)造成高額的內(nèi)存分配,React在啟動(dòng)時(shí)就會(huì)為每種對(duì)象分配內(nèi)存池,用到某一個(gè)事件對(duì)象時(shí)就可以從這個(gè)內(nèi)存池進(jìn)行復(fù)用,節(jié)省內(nèi)存。
再回到我們剛開始的問題,現(xiàn)在看起來就很沒有很費(fèi)解了,之所以會(huì)出現(xiàn)上面的問題是因?yàn)槲覀兓煊昧薘eact的事件機(jī)制和DOM原生的事件機(jī)制,認(rèn)為通過:
handleClickQr(e) { e.stopPropagation(); }
就能阻止原生的事件傳播,其實(shí)在事件委托的情形下是不能實(shí)現(xiàn)這一點(diǎn)的。當(dāng)然解決的辦法也不復(fù)雜,不要將React事件和DOM原生事件混用。
componentDidMount() { document.body.addEventListener("click", e => { this.setState({ active: false, }); }); document.querySelector(".code").addEventListener("click", e => { e.stopPropagation(); }) } componentWillUnmount() { document.body.removeEventListener("click"); document.querySelector(".qr").removeEventListener("click"); }
或者通過事件原件對(duì)象中的target進(jìn)行判斷:
componentDidMount() { document.body.addEventListener("click", e => { if (e.target && e.target.matches("div.code")) { return; } this.setState({ active: false, }); }); }
都可以解決異常關(guān)閉的問題。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82126.html
摘要:另外第三方也可以通過的事件插件機(jī)制來合成自定義事件,盡管很少人這么做。抽象跨平臺(tái)事件機(jī)制。打算干預(yù)事件的分發(fā)。事件是的一個(gè)自定義事件,旨在規(guī)范化表單元素的變動(dòng)事件。 showImg(https://segmentfault.com/img/remote/1460000019961124?w=713&h=307); 當(dāng)我們?cè)诮M件上設(shè)置事件處理器時(shí),React并不會(huì)在該DOM元素上直接綁定...
摘要:給注冊(cè)原生事件回調(diào)為統(tǒng)一的事件分發(fā)機(jī)制。根據(jù)元素唯一標(biāo)識(shí)和事件類型從中取出回調(diào)函數(shù)返回帶有合成事件參數(shù)的回調(diào)函數(shù)總流程將上面的四個(gè)流程串聯(lián)起來??梢?,回調(diào)函數(shù)是直接調(diào)用調(diào)用的,并沒有指定調(diào)用的組件,所以不進(jìn)行手動(dòng)綁定的情況下直接獲取到的是。 關(guān)于React事件的疑問 1.為什么要手動(dòng)綁定this 2.React事件和原生事件有什么區(qū)別 3.React事件和原生事件的執(zhí)行順序,可以混...
摘要:前言這是事件機(jī)制的第一篇,主要內(nèi)容有表象理解,驗(yàn)證,意義和思考。因?yàn)楹铣墒录挠|發(fā)是基于瀏覽器的事件機(jī)制來實(shí)現(xiàn)的,通過冒泡機(jī)制冒泡到最頂層元素,然后再由統(tǒng)一去處理。合成事件的阻止冒泡不會(huì)影響原生事件。 showImg(https://segmentfault.com/img/bVbtvP2?w=800&h=420); 前言 這是 react 事件機(jī)制的第一篇,主要內(nèi)容有:表象理解,驗(yàn)證...
摘要:事件簡介事件是合成事件,所有事件都自動(dòng)綁定到最外層上。支持事件的冒泡機(jī)制,我們可以使用和來中斷它。這樣做簡化了事件處理和回收機(jī)制,效率也有很大提升。事件類型合成事件的事件類型是原生事件類型的一個(gè)子集。 React事件簡介 React事件是合成事件,所有事件都自動(dòng)綁定到最外層上。因?yàn)閂irtual DOM 在內(nèi)存中是以對(duì)象的形式存在的,所以React 基于 Virtual DOM 實(shí)現(xiàn)了...
摘要:事件簡介事件是合成事件,所有事件都自動(dòng)綁定到最外層上。支持事件的冒泡機(jī)制,我們可以使用和來中斷它。這樣做簡化了事件處理和回收機(jī)制,效率也有很大提升。事件類型合成事件的事件類型是原生事件類型的一個(gè)子集。 React事件簡介 React事件是合成事件,所有事件都自動(dòng)綁定到最外層上。因?yàn)閂irtual DOM 在內(nèi)存中是以對(duì)象的形式存在的,所以React 基于 Virtual DOM 實(shí)現(xiàn)了...
閱讀 1416·2021-09-24 10:26
閱讀 1712·2019-08-30 14:14
閱讀 2130·2019-08-29 16:54
閱讀 387·2019-08-29 14:09
閱讀 1503·2019-08-29 12:55
閱讀 961·2019-08-28 18:13
閱讀 1605·2019-08-26 13:39
閱讀 2599·2019-08-26 11:43