【React】354- 一文吃透 React 事件機制原理


640?wx_fmt=png

大綱

主要分為4大塊兒,主要是結合源碼對 react事件機制的原理 進行分析,希望可以讓你對 react事件機制有更清晰的認識和理解。

當然肯定會存在一些表述不清或者理解不夠標准的地方,還請各位大神、大佬斧正。

01 - 對事件機制的初步理解和驗證

02 - 對於合成的理解

03 - 事件注冊機制

04 - 事件執行機制

01 02 是理論的廢話,也算是我的個人總結,沒興趣的可以直接跳到 03-事件執行機制。

ps: 本文基於 react15.6.1進行分析,雖然不是最新版本但是也不會影響我們對 react 事件機制的整體把握和理解。

對事件機制的初步理解和驗證

react事件機制 的表象理解,驗證,意義和思考。

表象理解

先回顧下 對react 事件機制基本理解,react自身實現了一套自己的事件機制,包括事件注冊、事件的合成、事件冒泡、事件派發等,雖然和原生的是兩碼事,但也是基於瀏覽器的事件機制下完成的。

我們都知道react 的所有事件並沒有綁定到具體的dom節點上而是綁定在了document 上,然后由統一的事件處理程序來處理,同時也是基於瀏覽器的事件機制(冒泡),所有節點的事件都會在 document 上觸發。

既然已經有了對 react事件 的一個基本的認知,那這個認知是否正確呢?我們可以通過簡單的方法進行驗證。

驗證

驗證內容:

所有事件均注冊到了元素的最頂層-document 上 節點的事件由統一的入口處理 為了方便,直接通過 cli 創建一個項目。


componentDidMount(){ 	
        document.getElementById('btn-reactandnative').addEventListener('click', (e) => {	
            console.log('原生+react 事件:   原生事件執行');	
        });	
    }	
    handleNativeAndReact = (e) => {	
        console.log('原生+react 事件:  當前執行react事件');	
    }	
    handleClick=(e)=>{	
        console.log('button click');	
    }	
 render(){	
        return <div className="pageIndex"><p>react event!!!</p	
                <button id="btn-confirm" onClick={this.handleClick}>react 事件</button>	
                <button id="btn-reactandnative" onClick={this.handleNativeAndReact}>原生 + react 事件</button>	
        </div>	
    }

代碼中給兩個 button綁定了合成事件,單獨給 btn#btn-reactandnative綁定了一個原生的事件。

然后看下 chrome 控制台,查看元素上注冊的事件。

640?wx_fmt=png

640?wx_fmt=png

經過簡單的驗證,可以看到所有的事件根據不同的事件類型都綁定在了 document 上,觸發函數統一是 dispatchEvent

試想一下

如果一個節點上同時綁定了合成和原生事件,那么禁止冒泡后執行關系是怎樣的呢?

其實讀到這里答案已經有了。我們現在基於目前的知識去分析下這個關系。

因為合成事件的觸發是基於瀏覽器的事件機制來實現的,通過冒泡機制冒泡到最頂層元素,然后再由 dispatchEvent統一去處理。

* 得出的結論:*

原生事件阻止冒泡肯定會阻止合成事件的觸發。

合成事件的阻止冒泡不會影響原生事件。

為什么呢?先回憶下瀏覽器事件機制

640?wx_fmt=png

瀏覽器事件的執行需要經過三個階段,捕獲階段-目標元素階段-冒泡階段。

節點上的原生事件的執行是在目標階段,然而合成事件的執行是在冒泡階段,所以原生事件會先合成事件執行,然后再往父節點冒泡。

既然原生都阻止冒泡了,那合成還執行個啥嘞。

好,輪到合成的被阻止冒泡了,那原生會執行嗎?當然會了。

因為原生的事件先於合成的執行,所以合成事件內阻止的只是合成的事件冒泡。(代碼我就不貼了)

所以得出結論:

原生事件(阻止冒泡)會阻止合成事件的執行

合成事件(阻止冒泡)不會阻止原生事件的執行

兩者最好不要混合使用,避免出現一些奇怪的問題

意義

react 自己做這么多的意義是什么?

  1. 減少內存消耗,提升性能,不需要注冊那么多的事件了,一種事件類型只在 document 上注冊一次

  2. 統一規范,解決 ie 事件兼容問題,簡化事件邏輯

  3. 對開發者友好

思考

既然 react 幫我們做了這么多事兒,那他的背后的機制是什么樣的呢?

事件怎么注冊的,事件怎么觸發的,冒泡機制怎樣實現的呢?

請繼續往后看......

對於合成的理解

剛聽說合成這個詞時候,感覺是特別高大上,很有深度,不是很好理解。

當我大概的了解過react事件機制后,略微了解一些皮毛,我覺得合成不單單是事件的合成和處理,從廣義上來說還包括:

  1. 對原生事件的封裝

  2. 對某些原生事件的升級和改造

  3. 不同瀏覽器事件兼容的處理

對原生事件的封裝

640?wx_fmt=png

上面代碼是給一個元素添加 click事件的回調方法,方法中的參數 e,其實不是原生事件對象而是react包裝過的對象,同時原生事件對象被放在了屬性 e.nativeEvent內。

通過調試,在執行棧里看下這個參數 e包含哪些屬性

640?wx_fmt=png

再看下官方說明文檔

640?wx_fmt=png

SyntheticEvent是react合成事件的基類,定義了合成事件的基礎公共屬性和方法。

react會根據當前的事件類型來使用不同的合成事件對象,比如鼠標單機事件 - SyntheticMouseEvent,焦點事件-SyntheticFocusEvent等,但是都是繼承自SyntheticEvent。

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

對原生事件的升級和改造

對於有些dom元素事件,我們進行事件綁定之后,react並不是只處理你聲明的事件類型,還會額外的增加一些其他的事件,幫助我們提升交互的體驗。

這里就舉一個例子來說明下:

當我們給input聲明個onChange事件,看下 react幫我們做了什么?

640?wx_fmt=png

可以看到react不只是注冊了一個onchange事件,還注冊了很多其他事件。

而這個時候我們向文本框輸入內容的時候,是可以實時的得到內容的。

然而原生只注冊一個onchange的話,需要在失去焦點的時候才能觸發這個事件,所以這個原生事件的缺陷react也幫我們彌補了。

ps: 上面紅色箭頭中有一個 invalid事件,這個並沒有注冊到document上,而是在具體的元素上。我的理解是這個是html5新增的一個事件,當輸入的數據不符合驗證規則的時候自動觸發,然而驗證規則和配置都要寫在當前input元素上,如果注冊到document上這個事件就無效了。

瀏覽器事件的兼容處理

react在給document注冊事件的時候也是對兼容性做了處理。

640?wx_fmt=png

上面這個代碼就是給document注冊事件,內部其實也是做了對 ie瀏覽器的兼容做了處理。

以上就是我對於react合成這個名詞的理解,其實react內部還處理了很多,我只是簡單的舉了幾個栗子,后面開始聊事件注冊和事件派發的機制。

事件注冊機制

這是 react 事件機制的第三節 - 事件注冊,在這里你將了解 react事件的注冊過程,以及在這個過程中主要經過了哪些關鍵步驟,同時結合源碼進行驗證和增強理解。

在這里並不會說非常細節的內容,而是把大概的流程和原理性的內容進行介紹,做到對整體流程有個認知和理解。

大致流程

react 事件注冊過程其實主要做了2件事:事件注冊、事件存儲。

a. 事件注冊 - 組件掛載階段,根據組件內的聲明的事件類型-onclick,onchange 等,給 document 上添加事件 -addEventListener,並指定統一的事件處理程序 dispatchEvent。

b. 事件存儲 - 就是把 react 組件內的所有事件統一的存放到一個對象里,緩存起來,為了在觸發事件的時候可以查找到對應的方法去執行。

640?wx_fmt=png

關鍵步驟

上面大致說了事件注冊需要完成的兩個目標,那完成目標的過程需要經過哪些關鍵處理呢?

首先 react 拿到將要掛載的組件的虛擬 dom(其實就是 react element 對象),然后處理 react dom 的 props ,判斷屬性內是否有聲明為事件的屬性,比如 onClick,onChange,這個時候得到事件類型 click,change 和對應的事件處理程序 fn,然后執行后面 3步

a. 完成事件注冊

b. 將 react dom ,事件類型,處理函數 fn 放入數組存儲

c. 組件掛載完成后,處理 b 步驟生成的數組,經過遍歷把事件處理函數存儲到 listenerBank(一個對象)

640?wx_fmt=png

源碼解析

從 jsx 說起

看個最熟悉的代碼,也是我們日常的寫法

//此處代碼省略	
    handleFatherClick=()=>{	
    }	
    handleChildClick=()=>{	
    }	
    render(){	
        return <div className="box">	
                    <div className="father" onClick={this.handleFatherClick}>	
                        <div className="child" onClick={this.handleChildClick}>child </div>	
                    </div>	
               </div>	
    }

經過 babel 編譯后,可以看到最終調用的方法是 react.createElement,z而且聲明的事件類型和回調就是個 props

640?wx_fmt=png

react.createElement執行的結果會返回一個所謂的虛擬 dom (react element object)

640?wx_fmt=png

處理組件props,拿到事件類型和回調 fn

ReactDOMComponent在進行組件加載(mountComponent)、更新(updateComponent)的時候,需要對props進行處理(_updateDOMProperties):

640?wx_fmt=png

可以看下 registrationNameModules 的內容,就不細說了,他就是一個內置的常量。

640?wx_fmt=png

事件注冊和事件的存儲

事件注冊

接着上面的代碼執行到了這個方法

enqueuePutListener(this, propKey, nextProp, transaction);

在這個方法里會進行事件的注冊以及事件的存儲,包括冒泡和捕獲的處理

640?wx_fmt=png

根據當前的組件實例獲取到最高父級-也就是document,然后執行方法 listenTo - 也是最關鍵的一個方法,進行事件綁定處理。

源碼文件:ReactBrowerEventEmitter.js

640?wx_fmt=png

最后執行 EventListener.listen(冒泡)或者 EventListener.capture(捕獲),單看下冒泡的注冊,其實就是 addEventListener的第三個參數是 false

640?wx_fmt=png

也可以看到注冊事件的時候也對 ie 瀏覽器做了兼容。

上面沒有看到 dispatchEvent 的定義,下面可以看到傳入 dispatchEvent 方法的代碼。

640?wx_fmt=png

到這里事件注冊就完事兒了。

事件存儲

開始事件的存儲,在 react 里所有事件的觸發都是通過 dispatchEvent方法統一進行派發的,而不是在注冊的時候直接注冊聲明的回調,來看下如何存儲的 。

react 把所有的事件和事件類型以及react 組件進行關聯,把這個關系保存在了一個 map里,也就是一個對象里(鍵值對),然后在事件觸發的時候去根據當前的 組件id和 事件類型查找到對應的 事件fn。

640?wx_fmt=png

結合源碼:

function enqueuePutListener(inst, registrationName, listener, transaction) {	
  var containerInfo = inst._hostContainerInfo;	
  var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;	
  var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;	
  listenTo(registrationName, doc);//這個方法上面已說完	
  //這里涉及到了事務,事物會在以后的章節再介紹,主要看事件注冊	
  //下面的代碼是將putListener放入數組,當組件掛載完后會依次執行數組的回調。也就是putListener會依次執行	
  transaction.getReactMountReady().enqueue(putListener, {	
    inst: inst,//組件實例	
    registrationName: registrationName,//事件類型 click	
    listener: listener //事件回調 fn	
  });	
}	
function putListener() {	
  var listenerToPut = this;	
  //放入數組,回調隊列	
  EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener);	
}

大致的流程就是執行完 listenTo(事件注冊),然后執行 putListener 方法進行事件存儲,所有的事件都會存儲到一個對象中 - listenerBank,具體由 EventPluginHub進行管理。

 //拿到組件唯一標識 id	
    var getDictionaryKey = function getDictionaryKey(inst) {	
      return '.' + inst._rootNodeID;	
    }	
   putListener: function putListener(inst, registrationName, listener) {	
    //得到組件 id	
        var key = getDictionaryKey(inst);	
        //得到listenerBank對象中指定事件類型的對象	
        var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});	
        //存儲回調 fn	
        bankForRegistrationName[key] = listener;	
        //....	
  }

listenerBank其實就是一個二級 map,這樣的結構更方便事件的查找。

這里的組件 id 就是組件的唯一標識,然后和fn進行關聯,在觸發階段就可以找到相關的事件回調。

640?wx_fmt=png

看到這個結構是不是很熟悉呢?就是我們平常使用的 object.

到這里大致的流程已經說完,是不是感覺有點明白又不大明白。

沒關系,再來個詳細的圖,重新理解下。

640?wx_fmt=png

事件執行機制

在事件注冊階段,最終所有的事件和事件類型都會保存到 listenerBank中。

那么在事件觸發的過程中上面這個對象有什么用處呢?

其實就是用來查找事件回調

大致流程

事件觸發過程總結為主要下面幾個步驟:

1.進入統一的事件分發函數(dispatchEvent)

2.結合原生事件找到當前節點對應的ReactDOMComponent對象

3.開始 事件的合成

3.1 根據當前事件類型生成指定的合成對象

3.2 封裝原生事件和冒泡機制

3.3 查找當前元素以及他所有父級

3.4 在 listenerBank查找事件回調並合成到 event(合成事件結束)

4.批量處理合成事件內的回調事件(事件觸發完成 end)

640?wx_fmt=png

舉個栗子

在說具體的流程前,先看一個栗子,后面的分析也是基於這個栗子

handleFatherClick=(e)=>{	
        console.log('father click');	
    }	
    handleChildClick=(e)=>{	
        console.log('child click');	
    }	
    render(){	
        return <div className="box">	
                    <div className="father" onClick={this.handleFatherClick}> father	
                        <div className="child" onClick={this.handleChildClick}>child </div>	
                    </div>	
               </div>	
    }

看到這個熟悉的代碼,我們就已經知道了執行結果。

當我點擊 child div 的時候,會同時觸發father的事件。

640?wx_fmt=png

源碼解析

dispatchEvent 進行事件分發

進入統一的事件分發函數 (dispatchEvent)。

當我點擊child div 的時候,這個時候瀏覽器會捕獲到這個事件,然后經過冒泡,事件被冒泡到 document 上,交給統一事件處理函數 dispatchEvent 進行處理。(上一文中我們已經說過 document 上已經注冊了一個統一的事件處理函數 dispatchEvent)。

640?wx_fmt=png

查找ReactDOMComponent

結合原生事件找到當前節點對應的 ReactDOMComponent對象,在原生事件對象內已經保留了對應的 ReactDOMComponent實例的引用,應該是在掛載階段就已經保存了。

640?wx_fmt=png

看下ReactDOMComponent實例的內容

640?wx_fmt=png

事件合成ing

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

640?wx_fmt=png

合成對象的生成

根據當前事件類型找到對應的合成類,然后進行合成對象的生成

//進行事件合成,根據事件類型獲得指定的合成類	
var SimpleEventPlugin = {	
    eventTypes: eventTypes,	
    extractEvents: function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {	
        var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];	
        //代碼已省略....	
        var EventConstructor;	
        switch (topLevelType) {	
            //代碼已省略....	
            case 'topClick'://【這里有一個不解的地方】 topLevelType = topClick,執行到這里了,但是這里沒有做任何操作	
                if (nativeEvent.button === 2) {	
                    return null;	
                }	
            //代碼已省略....	
            case 'topContextMenu'://而是會執行到這里,獲取到鼠標合成類	
                EventConstructor = SyntheticMouseEvent;	
                break;	
            case 'topAnimationEnd':	
            case 'topAnimationIteration':	
            case 'topAnimationStart':	
                EventConstructor = SyntheticAnimationEvent;//動畫類合成事件	
                break;	
            case 'topWheel':	
                EventConstructor = SyntheticWheelEvent;//鼠標滾輪類合成事件	
                break;	
            case 'topCopy':	
            case 'topCut':	
            case 'topPaste':	
                EventConstructor = SyntheticClipboardEvent;	
                break;	
        }	
        var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);	
        EventPropagators.accumulateTwoPhaseDispatches(event);	
        return event;//最終會返回合成的事件對象	
    }

封裝原生事件和冒泡機制

在這一步會把原生事件對象掛到合成對象的自身,同時增加事件的默認行為處理和冒泡機制

/**	
 *	
 * @param {obj} dispatchConfig 一個配置對象 包含當前的事件依賴 ["topClick"],冒泡和捕獲事件對應的名稱 bubbled: "onClick",captured: "onClickCapture"	
 * @param {obj} targetInst 組件實例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;	
    //處理事件的默認行為	
    if (defaultPrevented) {	
        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;	
    } else {	
        this.isDefaultPrevented = emptyFunction.thatReturnsFalse;	
    }	
    //處理事件冒泡 ,thatReturnsFalse 默認返回 false,就是不阻止冒泡	
    this.isPropagationStopped = emptyFunction.thatReturnsFalse;	
    return this;	
}

下面是增加的默認行為和冒泡機制的處理方法,其實就是改變了當前合成對象的屬性值, 調用了方法后屬性值為 true,就會阻止默認行為或者冒泡。

//在合成類原型上增加preventDefault和stopPropagation方法	
_assign(SyntheticEvent.prototype, {	
    preventDefault: function preventDefault() {	
        // ....略	
        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;	
    },	
    stopPropagation: function stopPropagation() {	
        //....略	
        this.isPropagationStopped = emptyFunction.thatReturnsTrue;	
    }	
);

看下 emptyFunction 代碼就明白了

640?wx_fmt=png

查找所有父級實例

根據當前節點實例查找他的所有父級實例存入path

/**	
 *	
 * @param {obj} inst 當前節點實例	
 * @param {function} fn 處理方法	
 * @param {obj} arg 合成事件對象	
 */	
function traverseTwoPhase(inst, fn, arg) {	
    var path = [];//存放所有實例 ReactDOMComponent	
    while (inst) {	
        path.push(inst);	
        inst = inst._hostParent;//層級關系	
    }	
    var i;	
    for (i = path.length; i-- > 0;) {	
        fn(path[i], 'captured', arg);//處理捕獲 ,反向處理數組	
    }	
    for (i = 0; i < path.length; i++) {	
        fn(path[i], 'bubbled', arg);//處理冒泡,從0開始處理,我們直接看冒泡	
    }	
}

看下 path 長啥樣

640?wx_fmt=png

事件合成結束

在listenerBank查找事件回調並合成到 event。

緊接着上面代碼

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

上面的代碼會調用下面這個方法,在 listenerBank中查找到事件回調,並存入合成事件對象。

/**EventPropagators.js	
 * 查找事件回調后,把實例和回調保存到合成對象內	
 * @param {obj} inst 組件實例	
 * @param {string} phase 事件類型	
 * @param {obj} event 合成事件對象	
 */	
function accumulateDirectionalDispatches(inst, phase, event) {	
    var listener = listenerAtPhase(inst, event, phase);	
    if (listener) {//如果找到了事件回調,則保存起來 (保存在了合成事件對象內)	
        event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);//把事件回調進行合並返回一個新數組	
        event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);//把組件實例進行合並返回一個新數組	
    }	
}	
/**	
 * EventPropagators.js	
 * 中間調用方法 拿到實例的回調方法	
 * @param {obj} inst  實例	
 * @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	
 * 拿到實例的回調方法	
 * @param {obj} inst 組件實例	
 * @param {string} registrationName Name of listener (e.g. `onClick`).	
 * @return {?function} 返回回調方法	
 */	
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];	
}

640?wx_fmt=png

為什么能夠查找到的呢?

因為 inst (組件實例)里有_rootNodeID,所以也就有了對應關系。

640?wx_fmt=png

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

批量處理事件合成對象

批量處理合成事件對象內的回調方法(事件觸發完成 end)。

生成完 合成事件對象后,調用棧回到了我們起初執行的方法內。

640?wx_fmt=png

//在這里執行事件的回調	
runEventQueueInBatch(events);

640?wx_fmt=png

到下面這一步中間省略了一些代碼,只貼出主要的代碼,下面方法會循環處理 合成事件內的回調方法,同時判斷是否禁止事件冒泡。

640?wx_fmt=png

貼上最后的執行回調方法的代碼

/**	
 *	
 * @param {obj} event 合成事件對象	
 * @param {boolean} simulated false	
 * @param {fn} listener 事件回調	
 * @param {obj} inst 組件實例	
 */	
function executeDispatch(event, simulated, listener, inst) {	
    var type = event.type || 'unknown-event';	
    event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);	
    if (simulated) {//調試環境的值為 false,按說生產環境是 true	
        //方法的內容請往下看	
        ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);	
    } else {	
        //方法的內容請往下看	
        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);//直接執行回調方法	
    } 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') {//非生產環境會通過自定義事件去觸發回調	
    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);	
        };	
    }	
}

640?wx_fmt=png

最后react 通過生成了一個臨時節點fakeNode,然后為這個臨時元素綁定事件處理程序,然后創建自定義事件 Event,通過fakeNode.dispatchEvent方法來觸發事件,並且觸發完畢之后立即移除監聽事件。

到這里事件回調已經執行完成,但是也有些疑問,為什么在非生產環境需要通過自定義事件來執行回調方法。可以看下上面的代碼在非生產環境對 ReactErrorUtils.invokeGuardedCallback 方法進行了重寫。

總結

主要是從整體流程上介紹了下 react事件的原理,其中並沒有深入到源碼的各個細節,包括事務處理、合成的細節等,另外梳理過程中自己也有一些疑惑的地方,感覺說原理還能比較容易理解一些,但是一結合源碼來寫就會覺得亂,因為 react代碼過於龐大,而且盤根錯節,很難抽離,對源碼有興趣的小伙兒可以深入研究下,當然還是希望本文能夠帶給你一些啟發,若文章有表述不清或有問題的地方歡迎留言、 交流、斧正。

參考資料

  • https://zhuanlan.zhihu.com/p/35468208

  • https://react.docschina.org/docs/events.html

原創系列推薦



4. 
5. 
6. 
7. 

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

640?wx_fmt=png
點這,與大家一起分享本文吧~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM