React筆記-事件分發

事件分發

之前講述了事件如何綁定在document上,那么具體事件觸發的時候是如何分發到具體的監聽者呢?我們接著上次注冊的事件代理看。當我點擊update counter按鈕時,觸發注冊的click事件代理。

function dispatchInteractiveEvent(topLevelType, nativeEvent) {interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
function interactiveUpdates(fn, a, b) {return _interactiveUpdatesImpl(fn, a, b);
}
var _interactiveUpdatesImpl = function (fn, a, b) {return fn(a, b);
};

topLevelTypeclicknativeEvent為真實dom事件對象。看似很多,其實就做了一件事: 執行dispatchEvent(topLevelType, nativeEvent)。其實不然,_interactiveUpdatesImpl在后面被重新賦值為interactiveUpdates$1,完成了一次自我蛻變。

function setBatchingImplementation(batchedUpdatesImpl, interactiveUpdatesImpl, flushInteractiveUpdatesImpl) {_batchedUpdatesImpl = batchedUpdatesImpl;_interactiveUpdatesImpl = interactiveUpdatesImpl;_flushInteractiveUpdatesImpl = flushInteractiveUpdatesImpl;
}function interactiveUpdates$1(fn, a, b) {if (!isBatchingUpdates && !isRendering && lowestPriorityPendingInteractiveExpirationTime !== NoWork) {performWork(lowestPriorityPendingInteractiveExpirationTime, false);lowestPriorityPendingInteractiveExpirationTime = NoWork;}var previousIsBatchingUpdates = isBatchingUpdates;isBatchingUpdates = true;try {return scheduler.unstable_runWithPriority(scheduler.unstable_UserBlockingPriority, function () {return fn(a, b);});} finally {isBatchingUpdates = previousIsBatchingUpdates;if (!isBatchingUpdates && !isRendering) {performSyncWork();}}
}setBatchingImplementation(batchedUpdates$1, interactiveUpdates$1, flushInteractiveUpdates$1);

如果有任何等待的交互更新,條件滿足的情況下會先同步更新,然后設置isBatchingUpdates,進行scheduler調度。最后同步更新。scheduler的各類優先級如下:

unstable_ImmediatePriority: 1
unstable_UserBlockingPriority: 2
unstable_NormalPriority: 3
unstable_LowPriority: 4
unstable_IdlePriority: 5

進入scheduler調度,根據優先級計算時間,開始執行傳入的回調函數。然后調用dispatchEvent,最后更新immediate workflushImmediateWork里的調用關系很復雜,最終會調用requestAnimationFrame進行更新,這里不進行過多討論。

function unstable_runWithPriority(priorityLevel, eventHandler) {switch (priorityLevel) {case ImmediatePriority:case UserBlockingPriority:case NormalPriority:case LowPriority:case IdlePriority:break;default:priorityLevel = NormalPriority;}var previousPriorityLevel = currentPriorityLevel;var previousEventStartTime = currentEventStartTime;currentPriorityLevel = priorityLevel;currentEventStartTime = exports.unstable_now();try {return eventHandler();} finally {currentPriorityLevel = previousPriorityLevel;currentEventStartTime = previousEventStartTime;flushImmediateWork();}
}

下面看看dispatchEvent的具體執行過程。

function dispatchEvent(topLevelType, nativeEvent) {if (!_enabled) {return;}// 獲取事件觸發的原始節點var nativeEventTarget = getEventTarget(nativeEvent);// 獲取原始節點最近的fiber對象(通過緩存在dom上的internalInstanceKey屬性來尋找),如果沒找到會往父節點繼續尋找。var targetInst = getClosestInstanceFromNode(nativeEventTarget);if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {targetInst = null;}// 創建對象,包含事件名稱,原始事件,目標fiber對象和ancestor(空數組);如果緩存池有則直接取出并根據參數初始化屬性。var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);try {// 批處理事件batchedUpdates(handleTopLevel, bookKeeping);} finally {// 釋放bookKeeping對象內存,并放入對象池緩存releaseTopLevelCallbackBookKeeping(bookKeeping);}
}

接著看batchedUpdates,其實就是設置isBatching變量然后調用handleTopLevel(bookkeeping)

function batchedUpdates(fn, bookkeeping) {if (isBatching) {return fn(bookkeeping);}isBatching = true;try {// _batchedUpdatesImpl其實指向batchedUpdates$1函數,具體細節這里不再贅述return _batchedUpdatesImpl(fn, bookkeeping);} finally {isBatching = false;var controlledComponentsHavePendingUpdates = needsStateRestore();if (controlledComponentsHavePendingUpdates) {_flushInteractiveUpdatesImpl();restoreStateIfNeeded();}}
}

所以將原始節點對應最近的fiber緩存在bookKeeping.ancestors中。

function handleTopLevel(bookKeeping) {var targetInst = bookKeeping.targetInst;var ancestor = targetInst;do {if (!ancestor) {bookKeeping.ancestors.push(ancestor);break;}var root = findRootContainerNode(ancestor);if (!root) {break;}bookKeeping.ancestors.push(ancestor);ancestor = getClosestInstanceFromNode(root);} while (ancestor);for (var i = 0; i < bookKeeping.ancestors.length; i++) {targetInst = bookKeeping.ancestors[i];runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));}
}

runExtractedEventsInBatch中調用了兩個方法: extractEventsrunEventsInBatch。前者構造合成事件,后者批處理合成事件。

function runExtractedEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);runEventsInBatch(events);
}

事件合成

 function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {var events = null;for (var i = 0; i < plugins.length; i++) {var possiblePlugin = plugins[i];if (possiblePlugin) {var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);if (extractedEvents) {events = accumulateInto(events, extractedEvents);}}}return events;
}

plugins是所有合成事件集合的數組,EventPluginHub初始化的時候完成注入。遍歷所有plugins,調用其extractEvents方法,返回構造的合成事件。accumulateInto函數則把合成事件放入events。本例click事件合適的pluginSimpleEventPlugin,其他plugin得到的extractedEvents都不滿足if (extractedEvents)條件。

EventPluginHubInjection.injectEventPluginsByName({SimpleEventPlugin: SimpleEventPlugin,EnterLeaveEventPlugin: EnterLeaveEventPlugin,ChangeEventPlugin: ChangeEventPlugin,SelectEventPlugin: SelectEventPlugin,BeforeInputEventPlugin: BeforeInputEventPlugin,
});

接下來看看構造合成事件的具體過程,這里針對SimpleEventPlugin,其他plugin就不一一分析了,來看下其extractEvents:

extractEvents: function(topLevelType, targetInst, nativeEvent, nativeEventTarget) {var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];if (!dispatchConfig) {return null;}var EventConstructor = void 0;switch (topLevelType) {...case TOP_CLICK:...EventConstructor = SyntheticMouseEvent;break;...    }var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);accumulateTwoPhaseDispatches(event);return event;}

topLevelEventsToDispatchConfig是一個map對象,存儲著各類事件對應的配置信息。這里獲取到click的配置信息,然后根據topLevelType選擇對應的合成構造函數,這里為SyntheticMouseEvent。接著從SyntheticMouseEvent合成事件對象池中獲取合成事件。調用EventConstructor.getPooled,最終調用的是getPooledEvent

注意: SyntheticEvent.extend方法中明確寫有addEventPoolingTo(Class);所以,SyntheticMouseEvent有eventPool、getPooled和release屬性。后面會詳細介紹SyntheticEvent.extend

function addEventPoolingTo(EventConstructor) {EventConstructor.eventPool = [];EventConstructor.getPooled = getPooledEvent;EventConstructor.release = releasePooledEvent;
}

首次觸發事件,對象池為空,所以這里需要新創建。如果不為空,則取出一個并初始化。

function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {var EventConstructor = this;if (EventConstructor.eventPool.length) {var instance = EventConstructor.eventPool.pop();EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);return instance;}return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
}

合成事件的屬性是由React主動生成的,一些屬性和原生事件的屬性名完全一致,使其完全符合W3C標準,因此在事件層面上具有跨瀏覽器兼容性。如果要訪問原生對象,通過nativeEvent屬性即可獲取。這里SyntheticMouseEventSyntheticUIEvent擴展而來,而SyntheticUIEventSyntheticEvent擴展而來。

var SyntheticMouseEvent = SyntheticUIEvent.extend({...
});var SyntheticUIEvent = SyntheticEvent.extend({...
});SyntheticEvent.extend = function (Interface) {var Super = this;// 原型繼承var E = function () {};E.prototype = Super.prototype;var prototype = new E();// 構造繼承function Class() {return Super.apply(this, arguments);}_assign(prototype, Class.prototype);Class.prototype = prototype;Class.prototype.constructor = Class;Class.Interface = _assign({}, Super.Interface, Interface);Class.extend = Super.extend;addEventPoolingTo(Class);return Class;
};

當被new創建時,會調用父類SyntheticEvent進行構造。主要是將原生事件上的屬性掛載到合成事件上,還配置了一些額外屬性。

function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {this.dispatchConfig = dispatchConfig;this._targetInst = targetInst;this.nativeEvent = nativeEvent;...
}

合成事件構造完成后,調用accumulateTwoPhaseDispatches

function accumulateTwoPhaseDispatches(events) {forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}// 循環處理所有的合成事件
function forEachAccumulated(arr, cb, scope) {if (Array.isArray(arr)) {arr.forEach(cb, scope);} else if (arr) {cb.call(scope, arr);}
}// 檢測事件是否具有捕獲階段和冒泡階段
function accumulateTwoPhaseDispatchesSingle(event) {if (event && event.dispatchConfig.phasedRegistrationNames) {traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);}
}function traverseTwoPhase(inst, fn, arg) {var path = [];// 循環遍歷當前元素及父元素,緩存至pathwhile (inst) {path.push(inst);inst = getParent(inst);}var i = void 0;// 捕獲階段for (i = path.length; i-- > 0;) {fn(path[i], 'captured', arg);}// 冒泡階段for (i = 0; i < path.length; i++) {fn(path[i], 'bubbled', arg);}
}function accumulateDirectionalDispatches(inst, phase, event) {// 獲取當前階段對應的事件處理函數var listener = listenerAtPhase(inst, event, phase);// 將相關listener和目標fiber掛載到event對應的屬性上if (listener) {event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);}
}

事件執行(批處理合成事件)

首先將events合并到事件隊列,之前沒有處理完畢的隊列也一同合并。如果新的事件隊列為空,則退出。反之開始循環處理事件隊列中每一個eventforEachAccumulated前面有提到過,這里不再贅述。

function runEventsInBatch(events) {if (events !== null) {eventQueue = accumulateInto(eventQueue, events);}var processingEventQueue = eventQueue;eventQueue = null;if (!processingEventQueue) {return;}forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);rethrowCaughtError();
}

接下來看看事件處理,executeDispatchesAndRelease方法將事件執行和事件清理分開。

var executeDispatchesAndReleaseTopLevel = function (e) {return executeDispatchesAndRelease(e);
};var executeDispatchesAndRelease = function (event) {if (event) {// 執行事件executeDispatchesInOrder(event);if (!event.isPersistent()) {// 事件清理,將合成事件放入對象池event.constructor.release(event);}}
};

提取事件的處理函數和對應的fiber,調用executeDispatch

function executeDispatchesInOrder(event) {var dispatchListeners = event._dispatchListeners;var dispatchInstances = event._dispatchInstances;if (Array.isArray(dispatchListeners)) {for (var i = 0; i < dispatchListeners.length; i++) {if (event.isPropagationStopped()) {break;}executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);}} else if (dispatchListeners) {executeDispatch(event, dispatchListeners, dispatchInstances);}event._dispatchListeners = null;event._dispatchInstances = null;
}

獲取真實dom掛載到event對象上,然后開始執行事件。

function executeDispatch(event, listener, inst) {var type = event.type || 'unknown-event';// 獲取真實domevent.currentTarget = getNodeFromInstance(inst);invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);event.currentTarget = null;
}

invokeGuardedCallbackAndCatchFirstError下面調用的方法很多,最終會來到invokeGuardedCallbackImpl,關鍵就在func.apply(context, funcArgs);這里的func就是listener(本例中是handleClick),而funcArgs就是合成事件對象。至此,事件執行完畢。

var invokeGuardedCallbackImpl = function (name, func, context, a, b, c, d, e, f) {var funcArgs = Array.prototype.slice.call(arguments, 3);try {func.apply(context, funcArgs);} catch (error) {this.onError(error);}
};

事件清理

事件執行完之后,剩下就是一些清理操作。event.constructor.release(event)相當于releasePooledEvent(event)。由于click對應的是SyntheticMouseEvent,所以會放入SyntheticMouseEvent.eventPool中。EVENT_POOL_SIZE固定為10。

function releasePooledEvent(event) {var EventConstructor = this;event.destructor();if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {EventConstructor.eventPool.push(event);}
}

這里做了兩件事,第一手動釋放event屬性上的內存(將屬性置為null),第二將event放入對象池。至此,清理工作完畢。

destructor: function () {...this.dispatchConfig = null;this._targetInst = null;this.nativeEvent = null;this.isDefaultPrevented = functionThatReturnsFalse;this.isPropagationStopped = functionThatReturnsFalse;this._dispatchListeners = null;this._dispatchInstances = null;...
}    

event清理完后,還會清理bookKeeping,同樣也會放入對象池進行緩存。同樣CALLBACK_BOOKKEEPING_POOL_SIZE也固定為10。

// callbackBookkeepingPool是react-dom中的全局變量
function releaseTopLevelCallbackBookKeeping(instance) {instance.topLevelType = null;instance.nativeEvent = null;instance.targetInst = null;instance.ancestors.length = 0;if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {callbackBookkeepingPool.push(instance);}
}

總結

最后執行performSyncWork。如果執行的事件內調用了this.setState,會進行reconciliationcommit。由于事件流的執行是批處理過程,同步調用this.setState不會立馬更新,需等待所有事件執行完成,即scheduler調度完后才開始performSyncWork,最終才能拿到新的state。如果是setTimeout或者是在dom上另外addEventListener的回調函數中調用this.setState則會立馬更新。因為執行回調函數的時候不經過React事件流。

更好的閱讀體驗在我的github,歡迎?提issue。

轉載于:https://www.cnblogs.com/raion/p/10598473.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275503.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275503.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275503.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

百度指數可視化_可視化指數

百度指數可視化Abstract:– Analysis of the visual representations of exponentials.– Proposals to solve current visualization issues.– Call to discussion to come up with a better visual representation convention.抽象&#xff1a; –分析指數的視覺表示形式。…

qemu+linux+x86+64,kvm 內部錯誤:無法找到適合 x86_64 的模擬器

本文將為您描述kvm 內部錯誤&#xff1a;無法找到適合 x86_64 的模擬器,教程操作方法:0x00 問題安裝完 KVM 之后&#xff0c;啟動管理工具報錯&#xff1a;內部錯誤&#xff1a;無法找到適合 x86_64 的模擬器于是查看 libvirtd 服務狀態&#xff0c;查看到以下內容&#xff1a;…

阿里云謙大佬:時間精力有限的情況下如何高效學習前端?

大家好&#xff0c;我是若川。最近組織了源碼共讀活動1個月&#xff0c;200人&#xff0c;一起讀了4周源碼&#xff0c;歡迎加我微信 ruochuan12 進群參與。今天分享一篇阿里云謙大佬的文章。昨天在群里也有小伙伴說到&#xff1a;大佬們是需要什么學什么&#xff0c;新手一般是…

JQuery小記

訪問dom元素 $代表整個dom tree $("#content") $("p") $("li .red") 字符串轉換為json對象 $.parseJSON ajax $.ajax({type: "post",url: "GetUser.ashx",success: function (data) {var t "";var json $.pars…

React個人整理

React基礎//ReactDOM.render(reactWhat,domWhere)在瀏覽器中渲染應用的一種途徑 //React.DOM表示預定義好的HTML元素集合 //React.DOM.h1(attributes,children)表示一個預定義的React 組件 //h1()第一個參數接收一個對象&#xff0c;用于指定該組件的任何屬性&#xff08;比如i…

sketch鋼筆工具_Sketch和Figma,不同的工具等于不同的結果

sketch鋼筆工具We like to compare the difference between various design programs and debate about which one is the most powerful. But we often forget to reflect on how using one of these tools is impacting our product. A powerful artist would say that he ca…

程序下載

Zaxis終端前置機 版 本下 載特 性1.20.1104.102ZaxisSetup.rar 分類: 程序下載轉載于:https://www.cnblogs.com/baijinlong/archive/2011/05/13/2045263.html

提升效率的Vue組件開發和實戰技巧

大家好我是若川。現在的大前端時代&#xff0c;是一個動蕩紛爭的時代&#xff0c;江湖中已經分成了很多門派&#xff0c;主要以Vue&#xff0c;React還有Angular為首&#xff0c;形成前端框架三足鼎立的局勢。Vue在前端框架中的地位就像曾經的 jQuery&#xff0c;由于其簡單易懂…

合格linux運維人員必會的30道shell編程面試題及講解

超深度講解shell高級編程實戰&#xff0c;截至目前shell編程課程國內培訓機構最細的課程&#xff0c;不信請看學員表現的水平。課程牛不牛&#xff0c;不是看老師、課表&#xff0c;而是看培養的的學生水平&#xff0c;目前全免費中伙伴們趕緊看啊。http://edu.51cto.com/cours…

linux下telnet失敗怎么處理,CentOS下telnet退出失敗的解決辦法

最近有CentOS用戶反映在調試網絡程序時出現了問題&#xff0c;服務雖然啟動了&#xff0c;但客戶端卻無法連接上&#xff0c;用telnet連接后發現是Windows防火墻的問題&#xff0c;可是用telnet命令連接成功后發現退不出去了&#xff0c;這該怎么辦&#xff1f;下面小編就給大家…

poj 1990

第一道樹狀數組 代碼&#xff1a; #include<iostream> #include<fstream>using namespace std;int n;struct e{int v,x; };e a[20001];long long cnt[20001],sum[20001];int lowbit(int x){return x&(x^(x-1)); }void modify(long long a[],int s,int t){while…

figma下載_Figma中的動態內容和顏色

figma下載First off, why use dynamic data?首先&#xff0c;為什么要使用動態數據&#xff1f; It’s easy to create and manage long lists of content 創建和管理一長串內容很容易 You get a better idea of what your product will look like with actual data 通過實際…

代碼自解釋不是不寫注釋的理由

有什么比花時間寫注釋更令人感到興奮的事情嗎&#xff1f;如果我沒有猜錯&#xff0c;你可能會說&#xff1a;“不好意思&#xff0c;所有事情都比寫注釋更令人感到興奮”。如果有人要你給代碼加上注釋&#xff0c;對你來說就像是一種侮辱。你的代碼寫得如此優雅&#xff0c;它…

linux匯編中的注釋,Linux 匯編器:對照 GAS 和 NASM

Linux 匯編器&#xff1a;對比 GAS 和 NASM轉自 http://www.ibm.com/developerworks/cn/linux/l-gas-nasm.html#ibm-pcon與其他語言不同&#xff0c;匯編語言要求開發人員了解編程所用機器的處理器體系結構。匯編程序不可移植&#xff0c;維護和理解常常比較麻煩&#xff0c;通…

你可能不知道的package.json

大家好&#xff0c;我是若川。最近組織了源碼共度活動&#xff1a;1個月&#xff0c;200人&#xff0c;一起讀了4周源碼&#xff0c;參與的小伙伴都表示收獲很大。如果感興趣可以點擊鏈接掃碼加我微信 ruochuan12。今天推薦一篇相對簡單的文章。前言在上一篇npm init vitejs/ap…

基于上下文的rpn_構建事物-產品評論視頻中基于上下文的情感分析

基于上下文的rpnThe word “Social” has taken a whole new meaning in today’s digital era. Simply going out to enjoy is no longer the only “social” criteria. Social now is — giving a peek in your personal and professional life to your connections. Facebo…

可愛的 Python: 使用 mechanize 和 Beautiful Soup 輕松收集 Web 數據

可愛的 Python: 使用 mechanize 和 Beautiful Soup 輕松收集 Web 數據 使用 Python 工具簡化 Web 站點數據的提取和組織 David Mertz, Ph.D., 開發人員, Gnosis Software, Inc.從 2000 年開始&#xff0c;David Mertz 就一直在為 developerWorks 專欄 Charming Python 和 XML M…

廣西工學院c語言試題答案,廣西工學院的C語言考試試題

廣西工學院鹿山學院 2005 — 2006 學年第 2 學期課程考核試題 考核課程 《C語言程序設計》 (A卷)考核班級 學生數 印數 考核方式 閉卷 考核時間 120 分鐘一、選擇題(每題2分&#xff0c;共40分)1. 一個C語言的源程序中&#xff0c; 。A&#xff0e;必須有一個主函數2. 下列數據…

JavaScript 斷點調試技巧

大家好&#xff0c;我是若川。最近組織了源碼共度活動&#xff1a;1個月&#xff0c;200人&#xff0c;一起讀了4周源碼&#xff0c;參與的小伙伴都表示收獲很大。如果感興趣可以點擊鏈接掃碼加我微信 ruochuan12。之前推薦過很多次調試文章&#xff0c;說明調試的重要性&#…

大學生電子設計大賽案例分析_為大學生設計問答平臺—案例研究

大學生電子設計大賽案例分析Dealing with academic-related questions like picking a course, fulfilling a major requirement can be tedious and ineffective when you have to simultaneously balance school work, social activities, and focus on personal growth and …