<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    簡(jiǎn)單聊聊React18事件系統(tǒng)

    共 8792字,需瀏覽 18分鐘

     ·

    2024-04-11 16:07


    前言

    在進(jìn)入正題之前,我們先思考一個(gè)問題,那就是事件系統(tǒng)重要嗎?

    事實(shí)上,前端應(yīng)用因?yàn)殡x用戶最近,所以會(huì)有很多交互邏輯,就會(huì)有很多事件與之綁定。正是有這些事件,才讓頁(yè)面‘活’起來(lái),才能用戶通過瀏覽器完成想要做的事情。所以事件系統(tǒng)對(duì)于用戶是非常重要的。

    一、React事件系統(tǒng)介紹

    對(duì)于不同的瀏覽器,對(duì)事件存在不同的兼容性,React 想實(shí)現(xiàn)一個(gè)兼容全瀏覽器的框架, 為了實(shí)現(xiàn)這個(gè)目標(biāo),就需要?jiǎng)?chuàng)建一個(gè)兼容全瀏覽器的事件系統(tǒng),以此抹平不同瀏覽器的差異。

    所以 React 也開發(fā)了一套自己的事件系統(tǒng)。正常在 React 中綁定事件,如下所示:

          
            const handleClick = ()=>{console.log('冒泡階段執(zhí)行')}
          
          
            <button onClick={handleClick} >event</button>如上所示給按鈕綁定了一個(gè) onClick事件,事件處理函數(shù)是 handleClick,那么真的就給 button 元素綁定事件了嗎?實(shí)際上并沒有,為了證實(shí)這一點(diǎn),打開瀏覽器調(diào)試工具,如下:
          
        

    如上所示,給按鈕綁定了一個(gè)  onClick 事件,事件處理函數(shù)是  handleClick ,那么真的就給 button 元素綁定事件了嗎?實(shí)際上并沒有,為了證實(shí)這一點(diǎn),打開瀏覽器調(diào)試工具,如圖1所示:

    9c6afe52bba9492218b552160fe5bacc.webp

    圖1

    可以看到在 Event Listeners 中,button的處理事件并不是 handleClick,而是一個(gè)空函數(shù) noop,這個(gè)函數(shù)是React底層綁定的。通過上面我們能知道,在 React 應(yīng)用中,我們所看到的 React 事件都是‘假’的!主要體現(xiàn)在:

    (1)給元素綁定的事件,不是真正的事件處理函數(shù)。 (2)甚至在事件處理函數(shù)中拿到的事件源e,也不是真正的事件源e。 1.事件系統(tǒng)介紹 在傳統(tǒng)的 DOM 事件中,事件模型是這樣樣的:事件捕獲階段 -> 事件執(zhí)行階段 -> 事件冒泡階段。 在React應(yīng)用中,也可以讓事件執(zhí)行在捕獲階段,或者是冒泡階段,以點(diǎn)擊事件為例子,當(dāng)給元素綁定onClick,執(zhí)行時(shí)機(jī)類似于冒泡階段,當(dāng)給元素綁定onClickCapture,執(zhí)行時(shí)機(jī)就類似于捕獲階段,我們來(lái)看一個(gè)Demo,如下所示:
          
            function Index(){
          
          
                const refObj = React.useRef(null)
          
          
                useEffect(()=>{
          
          
                    const handler = ()=>{
          
          
                        console.log('事件監(jiān)聽')
          
          
                    }
          
          
                    refObj.current.addEventListener('click',handler)
          
          
                    return () => {
          
          
                        refObj.current.removeEventListener('click',handler)
          
          
                    }
          
          
                },[])
          
          
                const handleClick = ()=>{
          
          
               console.log('冒泡階段執(zhí)行')
          
          
                }
          
          
                const handleCaptureClick = ()=>{
          
          
                   console.log('捕獲階段執(zhí)行')
          
          
                }
          
          
            return <button 
          
          
            ref={refObj} 
          
          
            onClick={handleClick} 
          
          
            onClickCapture={handleCaptureClick} >點(diǎn)擊</button>
          
          
            }
          
        

    通過onClick、onClickCapture和原生的DOM監(jiān)聽器給元素button綁定了三個(gè)事件處理函數(shù),當(dāng)觸發(fā)一次點(diǎn)擊事件的時(shí)候,處理函數(shù)的執(zhí)行,打印順序如下所示: 捕獲階段執(zhí)行 -> 事件監(jiān)聽 -> 冒泡階段執(zhí)行。 通過上面的打印結(jié)果,可以明白: 冒泡階段:開發(fā)者正常給 React 綁定的事件,比如onClick、onChange,執(zhí)行時(shí)機(jī)類似于冒泡階段。 捕獲階段:如果想要在類似捕獲階段執(zhí)行,可以將事件后面加上Capture后綴,比如 onClickCapture、onChangeCapture。 阻止事件冒泡:
          
            function Index(){
          
          
                const handleClick=(e)=> {
          
          
                    /* 阻止事件冒泡,handleFatherClick 事件將不在觸發(fā) */
          
          
                    e.stopPropagation() 
          
          
                }
          
          
                const handleFatherClick=()=> console.log('冒泡到父級(jí)')
          
          
                return <div onClick={ handleFatherClick } >
          
          
                    <div onClick={ handleClick } >點(diǎn)擊</div>
          
          
                </div>
          
          
            }
          
        

    React 阻止冒泡和原生事件中的寫法差不多,當(dāng)handleClick上阻止冒泡,父級(jí)元素的 handleFatherClick 將不再執(zhí)行,但是內(nèi)部實(shí)現(xiàn)上和原生的事件有差異。 2.阻止默認(rèn)行為 React 阻止默認(rèn)行為和原生的事件也有一些區(qū)別。 原生事件:e.preventDefault() 和return false可以用來(lái)阻止事件默認(rèn)行為,由于在React中給元素的事件并不是真正的事件處理函數(shù),所以導(dǎo)致return false方法在 React 應(yīng)用中完全失去了作用。 Reac事件:在 React 應(yīng)用中,可以用 e.preventDefault() 阻止事件默認(rèn)行為,這個(gè)方法并非是原生事件的 preventDefault,由于React事件源e也是獨(dú)立組建的,所以preventDefault也是單獨(dú)處理的。

    二、事件系統(tǒng)設(shè)計(jì)

    明白了 React 事件流中一些基礎(chǔ)細(xì)節(jié)之后,我們來(lái)看一下 React 事件系統(tǒng)是如何設(shè)計(jì)的。 1.事件可控性 我們知道在 React 運(yùn)行時(shí)中,有一個(gè)狀態(tài)可以反映出當(dāng)前更新上下文狀態(tài),那就是ExecutionContext,在React事件系統(tǒng)中觸發(fā)的事件,ExecutionContext會(huì)合并 EventContext,接下來(lái)在執(zhí)行上下文中,就可以通過EventContext判斷是否是事件內(nèi)部觸發(fā)的更新,也就能方便做一些事情,比如像legacy模式的批量更新。 設(shè)想一下,如果給真實(shí)的DOM綁定事件的話,那么用戶觸發(fā)DOM事件,React就不能及時(shí)感知到有事件觸發(fā)了,即便是可以通過事件監(jiān)聽器的方式,但是也很難改變事件觸發(fā)的上下文,還是前面的例子,如何讓事件執(zhí)行的時(shí)候,能夠判斷ExecutionContext中存在 EventContext,并且當(dāng)事件執(zhí)行完畢后,可以重置ExecutionContext狀態(tài)。 能夠解決上面問題的就是,讓React能夠感知到事件的觸發(fā),并且讓事件變成可控的。這樣給onClick綁定的事件處理函數(shù)handleClick就不能直接綁定在原生 DOM 上,而是由外層 App 統(tǒng)一做事件代理,再主動(dòng)去改變上下文狀態(tài),并且執(zhí)行事件處理函數(shù)。邏輯類似如下:
          
            /* 改變狀態(tài) */
          
          
            fn() 
          
          
            /* 執(zhí)行事件處理函數(shù) */
          
          
            /* 重置狀態(tài) */
          
        

    2.跨平臺(tái)兼容 React 并不僅僅能夠運(yùn)行在 Web 平臺(tái),同樣也適用于一些跨端的場(chǎng)景,比如 Taro RN,微信小程序等,在這些跨平臺(tái)場(chǎng)景中,是不能給元素綁定事件的,以微信小程序來(lái)說(shuō),雖然微信小程序是采用Webview的方式,但是對(duì)于原生DOM的操作,小程序并沒有給開發(fā)者開口子,也就是說(shuō)小程序里如果想要使用 React框架,就不能使用DOM的相關(guān)操作,也就不能直接綁定事件。但是 React事件系統(tǒng)的設(shè)計(jì),就能夠解決這個(gè)問題,因?yàn)?React的獨(dú)立的事件系統(tǒng),能夠把原生 DOM元素和事件執(zhí)行函數(shù)隔離開來(lái),統(tǒng)一管理事件,這樣事件的觸發(fā)由DOM層面變成了JS層面。為React做跨平臺(tái)兼容提供了技術(shù)支撐。 3.事件合成機(jī)制 React 對(duì)于事件的處理有一種事件合成的機(jī)制,首先需要弄清楚什么是事件合成? 本質(zhì)上來(lái)說(shuō)就是一個(gè)React事件,可能由多個(gè)原生事件合成。比如給input綁定一個(gè)onChange事件。
          
            function Index(){
          
          
              const handleChange =() => {}
          
          
              return <div >
          
          
                 <input onChange={ handleChange }  />
          
          
              </div>}
          
        

    在原生DOM中是沒有onChange事件的,對(duì)于onChange事件,原生事件中會(huì)有多個(gè)事件與之對(duì)應(yīng)。比如上面onChange事件,會(huì)綁定 blur、change、focus、keydown、keyup等多個(gè)事件。 在React應(yīng)用中,元素綁定的事件并不是原生事件,而是React合成的事件,比如onClick是由click合成,onChange是由 blur、change、focus 等多個(gè)事件合成。底層React用一個(gè)對(duì)象registrationNameDependencies保存React事件和合成的原生事件的映射關(guān)系。我們來(lái)看一下這個(gè)對(duì)象,如圖2所示。

    0b3c40dbd89264d43d90d60095a2979d.webp


    圖2

    當(dāng)然上面只是對(duì)象的一部分。事件系統(tǒng)大致思路:在React中有一套事件系統(tǒng)來(lái)處理DOM事件,React的事件系統(tǒng)大致可以分為三個(gè)部分來(lái)消化。 第一個(gè)部分是事件合成系統(tǒng),根據(jù)運(yùn)行的平臺(tái),做事件的初始化操作。第二個(gè)就是在一次渲染過程中,收集并處理標(biāo)簽中的事件。第三個(gè)就是一次用戶交互,事件觸發(fā),到事件執(zhí)行一系列過程。 我們看一下這三個(gè)部分的關(guān)聯(lián)和每一個(gè)部分都做了哪些事情。 上面說(shuō)到,React中的事件并不是注冊(cè)到真實(shí)DOM中的,而是通過事件系統(tǒng)統(tǒng)一處理的,首先就需要事件系統(tǒng)在初始化的時(shí)候,統(tǒng)一監(jiān)聽注冊(cè)這些事件。在React V18新版本中,會(huì)在入口函數(shù)中,統(tǒng)一注冊(cè)并監(jiān)聽事件,并且是在React root掛載容器上。在新版本React中,入口文件應(yīng)該像如下的樣子:
          
            const root = ReactDOM.createRoot(document.getElementById('app'))
          
        
    這個(gè)App就是綁定事件監(jiān)聽器的容器。在React V17 之前,React事件都是綁定在 document 上,React V17 之后,React把事件綁定在應(yīng)用對(duì)應(yīng)的容器container上,將事件綁定在同一容器統(tǒng)一管理。事件綁定采用的是 addEventListener 的方式。 4.事件統(tǒng)一處理函數(shù) 以React中點(diǎn)擊事件為例子,本質(zhì)上都是通過addEventListener進(jìn)行監(jiān)聽的,但是處理點(diǎn)擊事件的函數(shù)只有一個(gè),在事件處理函數(shù)中,可以通過事件源來(lái)找到點(diǎn)擊事件到底發(fā)生在哪個(gè) DOM 上,這個(gè)方式在傳統(tǒng)的事件流中叫作事件委托。 而在React中,也是收斂到一個(gè)函數(shù)中去執(zhí)行,也就是說(shuō),當(dāng)項(xiàng)目有很多個(gè)按鈕,無(wú)論點(diǎn)擊哪個(gè)按鈕,都會(huì)由同一個(gè)函數(shù)去處理并執(zhí)行,這個(gè)函數(shù)就是dispatchEvent。 5.冒泡和捕獲的處理 明白了事件注冊(cè)之后,那么還有一個(gè)問題,就是事件冒泡和捕獲是如何處理的呢? 為什么onClick會(huì)在事件冒泡階段執(zhí)行,而onClickCapture會(huì)在事件捕獲階段執(zhí)行呢? 想要解決這個(gè)問題也很容易,還是拿點(diǎn)擊事件click為例子,addEventListener在綁定事件的時(shí)候,可以通過第三個(gè)參數(shù)來(lái)確定是在冒泡階段執(zhí)行,還是在捕獲階段執(zhí)行:
          
            addEventListener(type, listener, useCapture)
          
        
    第一個(gè)參數(shù),事件名稱,字符串,必填,比如 click。第二個(gè)參數(shù),執(zhí)行函數(shù),必填。第三個(gè)參數(shù),觸發(fā)類型,布爾型,可以為空。true  事件在捕獲階段執(zhí)行,false 事件在冒泡階段執(zhí)行,默認(rèn)是 false。 言歸正傳,在綁定事件監(jiān)聽器的時(shí)候,綁定兩次就可以了,也就是在冒泡和捕獲階段各綁定一次。
          
            addEventListener('click',dispatchEvent$1,true)
          
          
            addEventListener('click',dispatchEvent$2,false)
          
        

    這樣 onClick 事件就可以在冒泡階段執(zhí)行,onClickCapture 事件也可以在捕獲階段執(zhí)行了。

    6.收集預(yù)處理事件 在整個(gè)應(yīng)用渲染階段的時(shí)候,遍歷fiber節(jié)點(diǎn)的時(shí)候,會(huì)對(duì)比props中的屬性,來(lái)對(duì)事件做預(yù)處理,在老版本 React 事件系統(tǒng)中,事件函數(shù)是在這個(gè)階段綁定的。 7.事件執(zhí)行 如果觸發(fā)一次點(diǎn)擊事件,那么在新版React中會(huì)觸發(fā)兩次React的統(tǒng)一處理函數(shù):第一次是捕獲執(zhí)行,onClick就會(huì)在此執(zhí)行。第二次是冒泡執(zhí)行,onClickCapture也會(huì)執(zhí)行了。這樣就保證了事件處理函數(shù)(例如onClick和onClickCapture)與原生的事件流保持一致。

    三、新老版本事件系統(tǒng)差異

    老版本事件系統(tǒng),在 React V17 以前的版本中,對(duì)于事件系統(tǒng)的處理有一些不同之處。我們還是以剛開始的Demo為例子,當(dāng)給button元素綁定 onClick、onClickCapture時(shí),還有一個(gè)事件監(jiān)聽器,當(dāng)觸發(fā)點(diǎn)擊事件的時(shí)候,新老版本打印的差異如下:
    新版本事件系統(tǒng):捕獲階段執(zhí)行 -> 事件監(jiān)聽 -> 冒泡階段執(zhí)行。 老版本事件系統(tǒng):事件監(jiān)聽 -> 捕獲階段執(zhí)行 -> 冒泡階段執(zhí)行。
    從前面直觀地看出新版本的事件是最接近原生的事件流的,老版本事件系統(tǒng)執(zhí)行順序差別更大一些,至于為什么我們馬上會(huì)講到。
    對(duì)于新老版本事件系統(tǒng)差異,還是比較大的,可以從事件初始化,事件執(zhí)行差異,事件收集差異。
    1.初始化差異
    與新版本不同的是,老版本事件系統(tǒng)初始化過程中,并沒有直接注冊(cè)事件,取而代之的是形成了一個(gè)事件插件對(duì)象 registrationNameModules。
    React有一種事件插件機(jī)制,比如上述onClick和onChange,會(huì)有不同的事件插件 SimpleEventPlugin、ChangeEventPlugin處理,先不必關(guān)心事件插件做了些什么,在后面會(huì)有相關(guān)的介紹。我們看一下老版本 registrationNameModule 長(zhǎng)什么樣子:
          
            const registrationNameModules = {
          
          
                onBlur: SimpleEventPlugin,
          
          
                onClick: SimpleEventPlugin,
          
          
            ...
          
          
            }
          
        

    registrationNameModules 記錄了React事件(比如onBlur)和與之對(duì)應(yīng)的處理插件映,比如上述的onClick,就會(huì)用SimpleEventPlugin 插件處理,onChange就會(huì)用ChangeEventPlugin處理。應(yīng)用于事件觸發(fā)階段,根據(jù)不同事件使用不同的插件。
    為什么要用不同的事件插件處理不同的React事件? 首先對(duì)于不同的事件,有不同的處理邏輯;對(duì)應(yīng)的事件源對(duì)象也有所不同,React的事件和事件源是自己合成的,所以對(duì)于不同事件需要不同的事件插件處理。
    2.事件收集差異
    在老版本事件系統(tǒng)中,在渲染階段會(huì)執(zhí)行事件的收集和綁定,上面說(shuō)到在老版本事件系統(tǒng)中,初始化階段,會(huì)處理props,比如發(fā)現(xiàn)了onClick事件,那么才向外層容器中綁定 click 事件,如果發(fā)現(xiàn)了onChange事件,才向容器中綁定 blur、change、focus 等事件,而不是在初始化過程中統(tǒng)一綁定的。
    3.事件執(zhí)行差異
    在事件執(zhí)行階段,老版本和新版本的事件系統(tǒng)也有本質(zhì)的區(qū)別:
    新版本事件系統(tǒng)會(huì)觸發(fā)兩次事件,分別是冒泡和捕獲事件,優(yōu)先執(zhí)行捕獲事件,onClickCapture等事件。接下來(lái)執(zhí)行冒泡事件,onClick事件。
    在老版本事件系統(tǒng)中,只會(huì)執(zhí)行一次事件,本質(zhì)上是在冒泡階段執(zhí)行的。而捕獲階段執(zhí)行的事件,是事件系統(tǒng)模擬的。具體如何模擬的呢?React 會(huì)在事件底層用一個(gè)數(shù)組隊(duì)列來(lái)收集fiber 樹上一條分支上的所有的onClick和onClickCapture事件,遇到捕獲階段執(zhí)行的事件,比如onClickCapture,就會(huì)通過unshift放在數(shù)組的前面,如果遇到冒泡階段執(zhí)行的事件,比如onClick,就會(huì)通過push放在數(shù)組的后面,最后依次執(zhí)行隊(duì)列中的事件處理函數(shù),模擬事件流。這個(gè)就是為什么老版本的事件系統(tǒng)執(zhí)行時(shí)機(jī)和真實(shí)的事件流相差很大的原因。
    最后我們用一幅圖描述一下,新老版本事件系統(tǒng)的差異,如圖3所示。

    d09e0b8272965d4fcd88e620b84495e0.webp圖3

    四、圖書推薦

    點(diǎn)擊封面圖片,了解圖書詳情
    本書講述了React各個(gè)模塊基礎(chǔ)和進(jìn)階用法,并提供了相應(yīng)的案例。還深入分析了React內(nèi)部運(yùn)轉(zhuǎn)機(jī)制,同時(shí)詳細(xì)介紹了React配套的生態(tài)系統(tǒng)。本書共14章,包括邂逅React、了解JSX、React組件、React更新驅(qū)動(dòng)、React生命周期、React狀態(tài)獲取與傳遞、工程化配置及跨平臺(tái)開發(fā)、React架構(gòu)設(shè)計(jì)、高性能React、React運(yùn)行時(shí)原理探秘、玩轉(zhuǎn)React Hooks、React-Router、React-Redux狀態(tài)管理工具和React實(shí)踐。 本書適合具有一定React開發(fā)基礎(chǔ),但希望更加全面、深入理解React的前端開發(fā)者閱讀。

    大咖推薦

    637e2cdadfb210e32b3adcb8efc8b959.webp

    作者介紹

    1652d3f112c36d46613d0f8c34a5b7c4.webp

    內(nèi)容結(jié)構(gòu)及配套資源

    5fae22e4c4e4ce84f0cc78076eb52c16.webp

    -End-

    撰  稿  人:計(jì)旭

    責(zé)任編輯:張淑謙

    審  核  人:時(shí)靜

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

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    欧美一级在线看 | 麻豆免费视频在线观看 | 日本色逼| 婷婷乱伦| xxx 一区|