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>

        60行代碼實(shí)現(xiàn)React的事件系統(tǒng)

        共 3710字,需瀏覽 8分鐘

         ·

        2022-03-04 22:52

        由于如下原因,React的事件系統(tǒng)代碼量很大:

        • 需要抹平不同瀏覽器的差異

        • 與內(nèi)部的「優(yōu)先級(jí)機(jī)制」綁定

        • 需要考慮所有瀏覽器事件

        但如果抽絲剝繭會(huì)發(fā)現(xiàn),事件系統(tǒng)的核心只有兩個(gè)模塊:

        • SyntheticEvent(合成事件)

        • 模擬實(shí)現(xiàn)的事件傳播機(jī)制

        本文會(huì)用60行代碼實(shí)現(xiàn)這兩個(gè)模塊,讓你快速了解React事件系統(tǒng)的原理。

        在線DEMO地址[1]

        Demo的效果

        對(duì)于如下這段JSX

        const?jsx?=?(
        ??<section?onClick={(e)?=>?console.log("click?section")}>
        ????<h3>你好h3>

        ????<button
        ??????onClick={(e)?=>
        ?{
        ????????//?e.stopPropagation();
        ????????console.log("click?button");
        ??????}}
        ????>
        ??????點(diǎn)擊
        ????button>
        ??section>
        );

        在瀏覽器中渲染:

        const?root?=?document.querySelector("#root");
        ReactDOM.render(jsx,?root);

        點(diǎn)擊按鈕,會(huì)依次打?。?/p>

        click?button
        click?section

        如果在button的點(diǎn)擊回調(diào)中增加e.stopPropagation(),點(diǎn)擊后會(huì)打?。?/p>

        click?button

        我們的目標(biāo)是將JSX中的onClick替換為ONCLICK,但是點(diǎn)擊后的效果不變。

        也就是說,我們將基于React自制一套事件系統(tǒng),他的事件名的書寫規(guī)則是形如「ONXXX」全大寫形式。

        實(shí)現(xiàn)SyntheticEvent

        首先,我們來實(shí)現(xiàn)SyntheticEvent(合成事件)。

        SyntheticEvent是瀏覽器原生事件對(duì)象的一層封裝。兼容所有瀏覽器,同時(shí)擁有和瀏覽器原生事件相同的API,如stopPropagation()preventDefault()。

        SyntheticEvent存在的目的是抹平瀏覽器間在事件對(duì)象間的差異,但是對(duì)于不支持某一事件的瀏覽器,SyntheticEvent并不會(huì)提供polyfill(因?yàn)檫@會(huì)顯著增大ReactDOM的體積)。

        我們的實(shí)現(xiàn)很簡單:

        class?SyntheticEvent?{
        ??constructor(e)?{
        ????this.nativeEvent?=?e;
        ??}
        ??stopPropagation()?{
        ????this._stopPropagation?=?true;
        ????if?(this.nativeEvent.stopPropagation)?{
        ??????this.nativeEvent.stopPropagation();
        ????}
        ??}
        }

        接收「原生事件對(duì)象」,返回一個(gè)包裝對(duì)象。原生事件對(duì)象會(huì)保存在nativeEvent屬性中。

        同時(shí),實(shí)現(xiàn)了stopPropagation方法。

        實(shí)際的SyntheticEvent會(huì)包含更多屬性和方法,這里為了演示目的簡化了

        實(shí)現(xiàn)事件傳播機(jī)制

        事件傳播機(jī)制的實(shí)現(xiàn)步驟如下:

        1. 在根節(jié)點(diǎn)綁定事件類型對(duì)應(yīng)的事件回調(diào),所有子孫節(jié)點(diǎn)觸發(fā)該類事件最終都會(huì)委托給「根節(jié)點(diǎn)的事件回調(diào)」處理。

        2. 尋找觸發(fā)事件的DOM節(jié)點(diǎn),找到其對(duì)應(yīng)的FiberNode(即虛擬DOM節(jié)點(diǎn))

        3. 收集從當(dāng)前FiberNode到根FiberNode之間所有注冊(cè)的「該事件對(duì)應(yīng)回調(diào)」

        4. 反向遍歷并執(zhí)行一遍所有收集的回調(diào)(模擬捕獲階段的實(shí)現(xiàn))

        5. 正向遍歷并執(zhí)行一遍所有收集的回調(diào)(模擬冒泡階段的實(shí)現(xiàn))

        首先,實(shí)現(xiàn)第一步:

        //?步驟1
        const?addEvent?=?(container,?type)?=>?{
        ??container.addEventListener(type,?(e)?=>?{
        ????//?dispatchEvent是需要實(shí)現(xiàn)的“根節(jié)點(diǎn)的事件回調(diào)”
        ????dispatchEvent(e,?type.toUpperCase(),?container);
        ??});
        };

        在入口處注冊(cè)點(diǎn)擊回調(diào)

        const?root?=?document.querySelector("#root");
        ReactDOM.render(jsx,?root);
        //?增加如下代碼
        addEvent(root,?"click");

        接下來實(shí)現(xiàn)「根節(jié)點(diǎn)的事件回調(diào)」

        const?dispatchEvent?=?(e,?type)?=>?{
        ??//?包裝合成事件
        ??const?se?=?new?SyntheticEvent(e);
        ??const?ele?=?e.target;
        ??
        ??//?比較hack的方法,通過DOM節(jié)點(diǎn)找到對(duì)應(yīng)的FiberNode
        ??let?fiber;
        ??for?(let?prop?in?ele)?{
        ????if?(prop.toLowerCase().includes("fiber"))?{
        ??????fiber?=?ele[prop];
        ????}
        ??}
        ??
        ??//?第三步:收集路徑中“該事件的所有回調(diào)函數(shù)”
        ??const?paths?=?collectPaths(type,?fiber);
        ??
        ??//?第四步:捕獲階段的實(shí)現(xiàn)
        ??triggerEventFlow(paths,?type?+?"CAPTURE",?se);
        ??
        ??//?第五步:冒泡階段的實(shí)現(xiàn)
        ??if?(!se._stopPropagation)?{
        ????triggerEventFlow(paths.reverse(),?type,?se);
        ??}
        };

        接下來收集路徑中「該事件的所有回調(diào)函數(shù)」。

        收集路徑中的事件回調(diào)函數(shù)

        實(shí)現(xiàn)的思路是:從當(dāng)前FiberNode一直向上遍歷,直到根FiberNode。收集遍歷過程中的FiberNode.memoizedProps屬性內(nèi)保存的「對(duì)應(yīng)事件回調(diào)」

        const?collectPaths?=?(type,?begin)?=>?{
        ??const?paths?=?[];
        ??
        ??//?不是根FiberNode的話,就一直向上遍歷
        ??while?(begin.tag?!==?3)?{
        ????const?{?memoizedProps,?tag?}?=?begin;
        ????
        ????//?5代表DOM節(jié)點(diǎn)對(duì)應(yīng)FiberNode
        ????if?(tag?===?5)?{
        ??????const?eventName?=?("on"?+?type).toUpperCase();
        ??????
        ??????//?如果包含對(duì)應(yīng)事件回調(diào),保存在paths中
        ??????if?(memoizedProps?&&?Object.keys(memoizedProps).includes(eventName))?{
        ????????const?pathNode?=?{};
        ????????pathNode[type.toUpperCase()]?=?memoizedProps[eventName];
        ????????paths.push(pathNode);
        ??????}
        ????}
        ????begin?=?begin.return;
        ??}
        ??
        ??return?paths;
        };

        得到的paths結(jié)構(gòu)類似如下:

        捕獲階段的實(shí)現(xiàn)

        由于我們是從目標(biāo)FiberNode向上遍歷,所以收集到的回調(diào)的順序是:

        [目標(biāo)事件回調(diào),?某個(gè)祖先事件回調(diào),?某個(gè)更久遠(yuǎn)的祖先回調(diào)?...]

        要模擬捕獲階段的實(shí)現(xiàn),需要從后向前遍歷數(shù)組并執(zhí)行回調(diào)。

        遍歷的方法如下:

        const?triggerEventFlow?=?(paths,?type,?se)?=>?{
        ??//?從后向前遍歷
        ??for?(let?i?=?paths.length;?i--;?)?{
        ????const?pathNode?=?paths[i];
        ????const?callback?=?pathNode[type];
        ????
        ????if?(callback)?{
        ??????//?存在回調(diào)函數(shù),傳入合成事件,執(zhí)行
        ??????callback.call(null,?se);
        ????}
        ????if?(se._stopPropagation)?{
        ??????//?如果執(zhí)行了se.stopPropagation(),取消接下來的遍歷
        ??????break;
        ????}
        ??}
        };

        注意,我們?cè)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">SyntheticEvent中實(shí)現(xiàn)的stopPropagation方法,調(diào)用后會(huì)阻止遍歷的繼續(xù)。

        冒泡階段的實(shí)現(xiàn)

        有了捕獲階段的實(shí)現(xiàn)經(jīng)驗(yàn),冒泡階段很容易實(shí)現(xiàn),只需將paths反向后再遍歷一遍就行。

        總結(jié)

        React事件系統(tǒng)的核心包括兩部分:

        • SyntheticEvent

        • 事件傳播機(jī)制

        事件傳播機(jī)制由5個(gè)步驟實(shí)現(xiàn)。

        總的來說,就是這么簡單。

        參考資料

        [1]

        在線DEMO地址: https://codesandbox.io/s/optimistic-torvalds-9ufc5?file=/src/index.js



        彥祖,點(diǎn)個(gè)「在看」
        瀏覽 73
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        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>
            男女拍拍视频网站 | 成 人 网 站 在 线 视 频不 | 在公交车上弄到高c小时说r杨震r | 天操中文字幕在线观看 | 久久福利一区 | 古代三级黄色片 | 黄 色 视 频高潮 | 日穴视频 | 欧美午夜精品久久久久久孕妇 | 天天操天天射天天添 |