1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 8666字,需瀏覽 18分鐘

         ·

        2020-11-25 06:02

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

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

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

        接下來和我一起開始學習吧~

        一、概念介紹

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

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

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

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

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

        1. 進行瀏覽器兼容,實現(xiàn)更好的跨平臺

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

        1. 避免垃圾回收

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

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

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

        二、原生事件回顧

        在開始介紹 React 合成事件之前,我們先簡單回顧 JavaScript 原生事件中幾個重要知識點:

        1. 事件捕獲

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

        2. 事件目標

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

        3. 事件冒泡

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

        4. 事件委托/事件代理

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

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

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

        1. 事件名稱命名方式不同

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

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

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

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

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

        3. 阻止默認行為方式不同

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

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

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

        4. 小結(jié)

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


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

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

        在 React 中,“合成事件”會以事件委托(Event Delegation)方式綁定在組件最上層,并在組件卸載(unmount)階段自動銷毀綁定的事件。這里我們手寫一個簡單示例來觀察 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)聽!");
        ????});
        ????this.childRef.current?.addEventListener("click",?()?=>?{
        ??????console.log("原生事件:子元素 DOM 事件監(jiān)聽!");
        ????});
        ????document.addEventListener("click",?(e)?=>?{
        ??????console.log("原生事件:document DOM 事件監(jiān)聽!");
        ????});
        ??}
        ??parentClickFun?=?()?=>?{
        ????console.log("React 事件:父元素事件監(jiān)聽!");
        ??};
        ??childClickFun?=?()?=>?{
        ????console.log("React 事件:子元素事件監(jiān)聽!");
        ??};
        ??render()?{
        ????return?(
        ??????<div?ref={this.parentRef}?onClick={this.parentClickFun}>
        ????????<div?ref={this.childRef}?onClick={this.childClickFun}>
        ??????????分析事件執(zhí)行順序
        ????????div>

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

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

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

        通過上面流程,我們可以理解:


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

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

        1. 事件池介紹

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

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

        Synthetic-Event-Loop.png

        (圖片來自: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("定時任務(wù) e.target:",?e.target);?//?null
        ????console.log("定時任務(wù):e:",?e);?
        ??},?100);
        }
        function?App()?{
        ??return?(
        ????<div?className="App">
        ??????<button?onClick={handleChange}>測試事件池button>

        ????div>
        ??);
        }

        export?default?App;

        可以看到輸出:

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

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

        再看下結(jié)果:

        Synthetic-Event-React17.png

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

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

        五、常見問題

        1. React 事件中 this 指向問題

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

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

        我們有 2 種方式解決這個問題:

        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 的方法改寫為使用箭頭函數(shù)定義:
        class?App?extends?React.Component<any,?any>?{
        ??clickFun?=?()?=>?{
        ????console.log("React?this?指向問題",?this.childClickFun);?//?undefined
        ??}
        ??
        ??//?省略其他代碼
        }
        export?default?App;

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

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

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

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

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

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

        這兩種方式是等價的:

        3. 合成事件阻止冒泡

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

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

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

        3.1 e.stopPropagation

        對于開發(fā)者來說,更希望使用 e.stopPropagation() 方法來阻止當前 DOM 事件冒泡,但事實上,從前兩節(jié)介紹的執(zhí)行順序可知,e.stopPropagation() 只能阻止合成事件間冒泡,即下層的合成事件,不會冒泡到上層的合成事件。事件本身還都是在 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)聽!");
        ????});
        ??}
        ??parentClickFun?=?(e:?any)?=>?{
        ????e.stopPropagation();
        ????console.log("阻止合成事件冒泡~");
        ??};
        ??render()?{
        ????return?(
        ??????this.parentRef}?onClick={this.parentClickFun}>
        ????????點擊測試“合成事件和原生事件是否可以混用”
        ??????</div>
        ????);
        ??}
        }
        export?default?App;

        輸出結(jié)果:

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

        3.2 e.nativeEvent.stopImmediatePropagation

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

        舉一個實際案例:實現(xiàn)點擊空白處關(guān)閉菜單的功能:當菜單打開時,在 document 上動態(tài)注冊事件,用來關(guān)閉菜單。

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

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

        合成事件和原生事件最好不要混用。原生事件中如果執(zhí)行了stopPropagation方法,則會導(dǎo)致其他React事件失效。因為所有元素的事件將無法冒泡到document上。通過前面介紹的兩者事件執(zhí)行順序來看,所有的 React 事件都將無法被注冊。通過代碼一起看看:

        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)聽!");
        ????});
        ??}
        ??parentClickFun?=?(e:?any)?=>?{
        ????console.log("阻止合成事件冒泡~");
        ??};
        ??render()?{
        ????return?(
        ??????this.parentRef}?onClick={this.parentClickFun}>
        ????????點擊測試“合成事件和原生事件是否可以混用”
        ??????</div>
        ????);
        ??}
        }
        export?default?App;

        輸出結(jié)果:

        阻止原生事件冒泡~?

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

        總結(jié)

        最后在回顧下本文學習目標:

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

        你是否都清楚了?歡迎一起討論學習。

        參考文章

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

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

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

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


        瀏覽 36
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            司机好大好硬好深好爽想要 | 男女午夜影院 | 国产一级毛片精品A片在线美传媒 | 国产精品久久久久久久久久久久 | 欧美二级片 | 成人电影无码在线观看 | 天堂8a 中文在线 | 久久视频一区二区 | 中文在线字 | 丰满大乳伦理少妇 |