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

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

          共 8792字,需瀏覽 18分鐘

           ·

          2024-04-11 16:07


          前言

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

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

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

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

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

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

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

          9c6afe52bba9492218b552160fe5bacc.webp

          圖1

          可以看到在 Event Listeners 中,button的處理事件并不是 handleClick,而是一個空函數(shù) noop,這個函數(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í)行在捕獲階段,或者是冒泡階段,以點擊事件為例子,當給元素綁定onClick,執(zhí)行時機類似于冒泡階段,當給元素綁定onClickCapture,執(zhí)行時機就類似于捕獲階段,我們來看一個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} >點擊</button>
                
                
                  }
                
              

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

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

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

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

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

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

          0b3c40dbd89264d43d90d60095a2979d.webp


          圖2

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

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

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

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

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

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

          d09e0b8272965d4fcd88e620b84495e0.webp圖3

          四、圖書推薦

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

          大咖推薦

          637e2cdadfb210e32b3adcb8efc8b959.webp

          作者介紹

          1652d3f112c36d46613d0f8c34a5b7c4.webp

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

          5fae22e4c4e4ce84f0cc78076eb52c16.webp

          -End-

          撰  稿  人:計旭

          責任編輯:張淑謙

          審  核  人:時靜

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  男人天堂影院 | 日本欧美一级色情片免费观看 | 黄色在线观看有限公司jb啊啊相当到位 | 五月丁香狠狠爱 | 中文字幕无码A |