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.mzph.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 …