成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

結(jié)合源碼徹底理解 react事件機(jī)制原理 04 - 事件執(zhí)行

marser / 1517人閱讀

摘要:文章涉及到的源碼是基于版本,雖然不是最新版本但是也不會影響我們對事件機(jī)制的整體把握和理解??偨Y(jié)本文主要是從整體流程上介紹了下事件觸發(fā)的過程。

前言

這是 react 事件機(jī)制的第四節(jié)-事件執(zhí)行,一起研究下在這個(gè)過程中主要經(jīng)過了哪些關(guān)鍵步驟,本文也是react 事件機(jī)制的完結(jié)篇,希望本文可以讓你對 react 事件執(zhí)行的原理有一定的理解。

文章涉及到的源碼是基于 react15.6.1版本,雖然不是最新版本但是也不會影響我們對 react 事件機(jī)制的整體把握和理解。

回顧

先簡單的回顧下上一文,事件注冊的結(jié)果是是把所有的事件回調(diào)保存到了一個(gè)對象中

那么在事件觸發(fā)的過程中上面這個(gè)對象有什么用處呢?

其實(shí)就是用來查找事件回調(diào)。

內(nèi)容大綱

按照我的理解,事件觸發(fā)過程總結(jié)為主要下面幾個(gè)步驟

1.進(jìn)入統(tǒng)一的事件分發(fā)函數(shù)(dispatchEvent)

2.結(jié)合原生事件找到當(dāng)前節(jié)點(diǎn)對應(yīng)的ReactDOMComponent對象

3.進(jìn)行事件的合成

3.1根據(jù)當(dāng)前事件類型生成指定的合成對象

3.2封裝原生事件和冒泡機(jī)制

3.3查找當(dāng)前節(jié)點(diǎn)以及他的所有父級

3.4在listenerBank查找事件回調(diào)并合成到 event(合成事件結(jié)束)

4.批量處理合成事件內(nèi)的回調(diào)事件(事件觸發(fā)完成 end)

說再多不如配個(gè)圖

舉個(gè)栗子

在說具體的流程前,先看一個(gè)栗子,后面的分析也是基于這個(gè)栗子

handleFatherClick=(e)=>{
        console.log("father click");
    }

    handleChildClick=(e)=>{
        console.log("child click");
    }

    render(){
        return 
father
child
}

看到這個(gè)熟悉的代碼,我們就已經(jīng)知道了執(zhí)行結(jié)果。

當(dāng)我點(diǎn)擊 child div 的時(shí)候,會同時(shí)觸發(fā)father的事件。

1、進(jìn)入統(tǒng)一的事件分發(fā)函數(shù) (dispatchEvent)

當(dāng)我點(diǎn)擊child div 的時(shí)候,這個(gè)時(shí)候?yàn)g覽器會捕獲到這個(gè)事件,然后經(jīng)過冒泡,事件被冒泡到 document 上,交給統(tǒng)一事件處理函數(shù) dispatchEvent 進(jìn)行處理。(上一文中我們已經(jīng)說過 document 上已經(jīng)注冊了一個(gè)統(tǒng)一的事件處理函數(shù) dispatchEvent)

2、結(jié)合原生事件找到當(dāng)前節(jié)點(diǎn)對應(yīng)的ReactDOMComponent對象

在原生事件對象內(nèi)已經(jīng)保留了對應(yīng)的ReactDOMComponent實(shí)例,應(yīng)該是在掛載階段就已經(jīng)保存了

看下ReactDOMComponent實(shí)例的內(nèi)容

3、開始進(jìn)行事件合成

事件的合成,冒泡的處理以及事件回調(diào)的查找都是在合成階段完成的。

3.1 根據(jù)當(dāng)前事件類型找到對應(yīng)的合成類,然后進(jìn)行合成對象的生成

//進(jìn)行事件合成,根據(jù)事件類型獲得指定的合成類
var SimpleEventPlugin = {
    eventTypes: eventTypes,
    extractEvents: function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
        var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
        //代碼已省略....
        var EventConstructor;

        switch (topLevelType) {
            //代碼已省略....
            case "topClick"://【這里有一個(gè)不解的地方】 topLevelType = topClick,執(zhí)行到這里了,但是這里沒有做任何操作
                if (nativeEvent.button === 2) {
                    return null;
                }
            //代碼已省略....
            case "topContextMenu"://而是會執(zhí)行到這里,獲取到鼠標(biāo)合成類
                EventConstructor = SyntheticMouseEvent;
                break;


            case "topAnimationEnd":
            case "topAnimationIteration":
            case "topAnimationStart":
                EventConstructor = SyntheticAnimationEvent;//動畫類合成事件
                break;

            case "topWheel":
                EventConstructor = SyntheticWheelEvent;//鼠標(biāo)滾輪類合成事件
                break;

            case "topCopy":
            case "topCut":
            case "topPaste":
                EventConstructor = SyntheticClipboardEvent;
                break;
        }

        var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
        EventPropagators.accumulateTwoPhaseDispatches(event);
        return event;//最終會返回合成的事件對象
    }

3.2 封裝原生事件和冒泡機(jī)制

在這一步會把原生事件對象掛到合成對象的自身,同時(shí)增加事件的默認(rèn)行為處理和冒泡機(jī)制

/**
 * 
 * @param {obj} dispatchConfig 一個(gè)配置對象 包含當(dāng)前的事件依賴 ["topClick"],冒泡和捕獲事件對應(yīng)的名稱 bubbled: "onClick",captured: "onClickCapture"
 * @param {obj} targetInst 組件實(shí)例ReactDomComponent
 * @param {obj} nativeEvent 原生事件對象
 * @param {obj} nativeEventTarget  事件源 e.target = div.child
 */
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {

    this.dispatchConfig = dispatchConfig;
    this._targetInst = targetInst;
    this.nativeEvent = nativeEvent;//將原生對象保存到 this.nativeEvent
    //此處代碼略.....
    var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;

    //處理事件的默認(rèn)行為
    if (defaultPrevented) {
        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    } else {
        this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
    }


    //處理事件冒泡 ,thatReturnsFalse 默認(rèn)返回 false,就是不阻止冒泡
    this.isPropagationStopped = emptyFunction.thatReturnsFalse;
    return this;
}

下面是增加的默認(rèn)行為和冒泡機(jī)制的處理方法,其實(shí)就是改變了當(dāng)前合成對象的屬性值, 調(diào)用了方法后屬性值為 true,就會阻止默認(rèn)行為或者冒泡。

來看下代碼

//在合成類原型上增加preventDefault和stopPropagation方法
_assign(SyntheticEvent.prototype, {
    preventDefault: function preventDefault() {
        // ....略

        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    },
    stopPropagation: function stopPropagation() {
        //....略

        this.isPropagationStopped = emptyFunction.thatReturnsTrue;
    }
);

看下 emptyFunction 代碼就明白了

3.3 根據(jù)當(dāng)前節(jié)點(diǎn)實(shí)例查找他的所有父級實(shí)例存入path

/**
 * 
 * @param {obj} inst 當(dāng)前節(jié)點(diǎn)實(shí)例
 * @param {function} fn 處理方法
 * @param {obj} arg 合成事件對象
 */
function traverseTwoPhase(inst, fn, arg) {
    var path = [];//存放所有實(shí)例 ReactDOMComponent

    while (inst) {
        path.push(inst);
        inst = inst._hostParent;//層級關(guān)系
    }

    var i;

    for (i = path.length; i-- > 0;) {
        fn(path[i], "captured", arg);//處理捕獲 ,反向處理數(shù)組
    }

    for (i = 0; i < path.length; i++) {
        fn(path[i], "bubbled", arg);//處理冒泡,從0開始處理,我們直接看冒泡
    }
}

看下 path 長啥樣

3.4 在listenerBank查找事件回調(diào)并合成到 event(事件合成結(jié)束)

緊接著上面代碼

 fn(path[i], "bubbled", arg);

上面的代碼會調(diào)用下面這個(gè)方法,在listenerBank中查找到事件回調(diào),并存入合成事件對象。

/**EventPropagators.js
 * 查找事件回調(diào)后,把實(shí)例和回調(diào)保存到合成對象內(nèi)
 * @param {obj} inst 組件實(shí)例
 * @param {string} phase 事件類型
 * @param {obj} event 合成事件對象
 */
function accumulateDirectionalDispatches(inst, phase, event) {
    var listener = listenerAtPhase(inst, event, phase);
    if (listener) {//如果找到了事件回調(diào),則保存起來 (保存在了合成事件對象內(nèi))
        event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);//把事件回調(diào)進(jìn)行合并返回一個(gè)新數(shù)組
        event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);//把組件實(shí)例進(jìn)行合并返回一個(gè)新數(shù)組
    }
}

/**
 * EventPropagators.js
 * 中間調(diào)用方法 拿到實(shí)例的回調(diào)方法
 * @param {obj} inst  實(shí)例
 * @param {obj} event 合成事件對象
 * @param {string} propagationPhase 名稱,捕獲capture還是冒泡bubbled
 */
function listenerAtPhase(inst, event, propagationPhase) {
    var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
    return getListener(inst, registrationName);
}

/**EventPluginHub.js
 * 拿到實(shí)例的回調(diào)方法
 * @param {obj} inst 組件實(shí)例
 * @param {string} registrationName Name of listener (e.g. `onClick`).
 * @return {?function} 返回回調(diào)方法
 */
getListener: function getListener(inst, registrationName) {
    var bankForRegistrationName = listenerBank[registrationName];

    if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
        return null;
    }

    var key = getDictionaryKey(inst);
    return bankForRegistrationName && bankForRegistrationName[key];
}

這里要高亮一下

為什么能夠查找到的呢?
因?yàn)?inst (組件實(shí)例)里有_rootNodeID,所以也就有了對應(yīng)關(guān)系


到這里事件合成對象生成完成,所有的事件回調(diào)已保存到了合成對象中。

4、 批量處理合成事件對象內(nèi)的回調(diào)方法(事件觸發(fā)完成 end)

第3步生成完 合成事件對象后,調(diào)用?;氐搅宋覀兤鸪鯃?zhí)行的方法內(nèi)

//在這里執(zhí)行事件的回調(diào)
runEventQueueInBatch(events);

到下面這一步中間省略了一些代碼,只貼出主要的代碼,

下面方法會循環(huán)處理 合成事件內(nèi)的回調(diào)方法,同時(shí)判斷是否禁止事件冒泡。

貼上最后的執(zhí)行回調(diào)方法的代碼

/**
 * 
 * @param {obj} event 合成事件對象
 * @param {boolean} simulated false
 * @param {fn} listener 事件回調(diào)
 * @param {obj} inst 組件實(shí)例
 */
function executeDispatch(event, simulated, listener, inst) {
    var type = event.type || "unknown-event";
    event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);

    if (simulated) {//調(diào)試環(huán)境的值為 false,按說生產(chǎn)環(huán)境是 true 
        //方法的內(nèi)容請往下看
        ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
    } else {
        //方法的內(nèi)容請往下看
        ReactErrorUtils.invokeGuardedCallback(type, listener, event);
    }

    event.currentTarget = null;
}

/** ReactErrorUtils.js
 * @param {String} name of the guard to use for logging or debugging
 * @param {Function} func The function to invoke
 * @param {*} a First argument
 * @param {*} b Second argument
 */
var caughtError = null;
function invokeGuardedCallback(name, func, a) {
    try {
        func(a);//直接執(zhí)行回調(diào)方法
    } catch (x) {
        if (caughtError === null) {
            caughtError = x;
        }
    }
}

var ReactErrorUtils = {
    invokeGuardedCallback: invokeGuardedCallback,
    invokeGuardedCallbackWithCatch: invokeGuardedCallback,
    rethrowCaughtError: function rethrowCaughtError() {
        if (caughtError) {
            var error = caughtError;
            caughtError = null;
            throw error;
        }
    }
};

if (process.env.NODE_ENV !== "production") {//非生產(chǎn)環(huán)境會通過自定義事件去觸發(fā)回調(diào)
    if (typeof window !== "undefined" && typeof window.dispatchEvent === "function" && typeof document !== "undefined" && typeof document.createEvent === "function") {
        var fakeNode = document.createElement("react");

        ReactErrorUtils.invokeGuardedCallback = function (name, func, a) {
            var boundFunc = func.bind(null, a);
            var evtType = "react-" + name;
            fakeNode.addEventListener(evtType, boundFunc, false);
            var evt = document.createEvent("Event");
            evt.initEvent(evtType, false, false);
            fakeNode.dispatchEvent(evt);
            fakeNode.removeEventListener(evtType, boundFunc, false);
        };
    }
}

最后react 通過生成了一個(gè)臨時(shí)節(jié)點(diǎn)fakeNode,然后為這個(gè)臨時(shí)元素綁定事件處理程序,然后創(chuàng)建自定義事件 Event,通過fakeNode.dispatchEvent方法來觸發(fā)事件,并且觸發(fā)完畢之后立即移除監(jiān)聽事件。

到這里事件回調(diào)已經(jīng)執(zhí)行完成,但是也有些疑問,為什么在非生產(chǎn)環(huán)境需要通過自定義事件來執(zhí)行回調(diào)方法。可以看下上面的代碼在非生產(chǎn)環(huán)境對ReactErrorUtils.invokeGuardedCallback 方法進(jìn)行了重寫。

5、總結(jié)

本文主要是從整體流程上介紹了下 react 事件觸發(fā)的過程。

主要流程有:

進(jìn)入統(tǒng)一的事件分發(fā)函數(shù)(dispatchEvent)

結(jié)合原生事件找到當(dāng)前節(jié)點(diǎn)對應(yīng)的ReactDOMComponent對象

進(jìn)行事件的合成

3.1 根據(jù)當(dāng)前事件類型生成指定的合成對象

3.2 封裝原生事件和冒泡機(jī)制

3.3 查找當(dāng)前節(jié)點(diǎn)以及他的所有父級

3.4 在listenerBank查找事件回調(diào)并合成到 event(事件合成結(jié)束)

4.批量處理合成事件內(nèi)的回調(diào)事件(事件觸發(fā)完成 end)

其中并沒有深入到源碼的細(xì)節(jié),包括事務(wù)處理、合成的細(xì)節(jié)等,另外梳理過程中自己也有一些疑惑的地方,對源碼有興趣的小伙兒可以深入研究下,當(dāng)然還是希望本文能夠帶給你一些啟發(fā),若文章有表述不清或有問題的地方歡迎留言交流。

更多精彩內(nèi)容歡迎關(guān)注我的公眾號 - 前端張大胖

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/104429.html

相關(guān)文章

  • 結(jié)合源碼徹底理解 react事件機(jī)制原理 01 - 對事件機(jī)制的初步理解和驗(yàn)證

    摘要:前言這是事件機(jī)制的第一篇,主要內(nèi)容有表象理解,驗(yàn)證,意義和思考。因?yàn)楹铣墒录挠|發(fā)是基于瀏覽器的事件機(jī)制來實(shí)現(xiàn)的,通過冒泡機(jī)制冒泡到最頂層元素,然后再由統(tǒng)一去處理。合成事件的阻止冒泡不會影響原生事件。 showImg(https://segmentfault.com/img/bVbtvP2?w=800&h=420); 前言 這是 react 事件機(jī)制的第一篇,主要內(nèi)容有:表象理解,驗(yàn)證...

    muddyway 評論0 收藏0
  • 結(jié)合源碼徹底理解 react事件機(jī)制原理 03 - 事件注冊

    摘要:文章涉及到的源碼是基于版本,雖然不是最新版本但是也不會影響我們對事件機(jī)制的整體把握和理解。到這里事件注冊就完事兒了。 showImg(https://segmentfault.com/img/bVbtvI3?w=1048&h=550); 前言 這是 react 事件機(jī)制的第三節(jié) - 事件注冊,通過本文你將了解react 事件的注冊過程,以及在這個(gè)過程中主要經(jīng)過了哪些關(guān)鍵步驟,同時(shí)結(jié)合源...

    chaosx110 評論0 收藏0
  • 結(jié)合源碼徹底理解 react事件機(jī)制原理 02 - 對于合成的理解

    摘要:前言這是事件機(jī)制系列文章的第二篇對于合成的理解,咱們就來說說合成這個(gè)名詞。在給注冊事件的時(shí)候也是對兼容性做了處理??偨Y(jié)以上就是我對于合成這個(gè)名詞的理解,其實(shí)內(nèi)部還處理了很多,我只是略微簡單的舉了幾個(gè)栗子。 showImg(https://segmentfault.com/img/bVbtvI3?w=1048&h=550); 前言 這是react事件機(jī)制系列文章的第二篇-對于合成的理解,...

    nihao 評論0 收藏0
  • 前端小冊 - 結(jié)合源碼徹底理解 react 事件機(jī)制

    摘要:對事件機(jī)制的初步理解和驗(yàn)證對于合成的理解事件注冊機(jī)制事件執(zhí)行本文基于進(jìn)行分析,雖然不是最新版本但是也不會影響我們對事件機(jī)制的整體把握和理解。最后希望通過本文可以讓你對事件機(jī)制有更清晰的認(rèn)識和理解。 showImg(https://segmentfault.com/img/bVbtvI3?w=1048&h=550); 前言 寫這個(gè)文章也算是實(shí)現(xiàn)19年的一個(gè) flag,研究一個(gè)知識點(diǎn)并且把...

    YJNldm 評論0 收藏0
  • 優(yōu)秀文章收藏(慢慢消化)持續(xù)更新~

    摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實(shí)驗(yàn)室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類面試問題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...

    JeOam 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<