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); };topLevelType為click,nativeEvent為真實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 work。flushImmediateWork里的調用關系很復雜,最終會調用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中調用了兩個方法: extractEvents和runEventsInBatch。前者構造合成事件,后者批處理合成事件。
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事件合適的plugin是SimpleEventPlugin,其他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屬性即可獲取。這里SyntheticMouseEvent由SyntheticUIEvent擴展而來,而SyntheticUIEvent由SyntheticEvent擴展而來。
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合并到事件隊列,之前沒有處理完畢的隊列也一同合并。如果新的事件隊列為空,則退出。反之開始循環處理事件隊列中每一個event。forEachAccumulated前面有提到過,這里不再贅述。
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,會進行reconciliation和commit。由于事件流的執行是批處理過程,同步調用this.setState不會立馬更新,需等待所有事件執行完成,即scheduler調度完后才開始performSyncWork,最終才能拿到新的state。如果是setTimeout或者是在dom上另外addEventListener的回調函數中調用this.setState則會立馬更新。因為執行回調函數的時候不經過React事件流。
更好的閱讀體驗在我的github,歡迎?提issue。
轉載于:https://www.cnblogs.com/raion/p/10598473.html
總結
以上是生活随笔為你收集整理的React笔记-事件分发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3308):redux项目创建
- 下一篇: 前端学习(3204):类式组件