手机版

浅谈反应事件实现原理

时间:2021-08-29 来源:互联网 编辑:宝哥软件园 浏览:

反应元素的事件处理和数字正射影像图元素的很相似。但是有一点语法上的不同:

反应事件绑定属性的命名采用驼峰式写法,而不是小写。如果采用小艾的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)并且反应自己内部实现了一个合成事件,使用反应的时候通常你不需要使用听器为一个已创建的数字正射影像图元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。

我们看一下这是怎么实现的

反应事件机制分为事件注册,和事件分发,两个部分

事件注册

//事件绑定函数handleClick(e){ e . prevent default();控制台日志('链接已被单击');}返回(a href=' # ' rel='外部无跟随' OnClick={ handleClick }单击我/a);上述代码中,onClick作为一个小道具传入了一个手柄点击,在组件更新和挂载的时候,会对小道具处理,事件绑定流程如下:

核心代码:

在ReactDOMComponent.js进行组件加载(安装组件)、更新(更新组件)的时候,调用_updateDOMProperties方法对小道具进行处理:

ReactDOMComponent.js

_更新属性:函数(最后一个道具,下一个道具,交易){ 0.if(RegistrationNameModules。hasown property(PropKey)){ if(NextProp){//如果传入的是事件,去注册事件enqueuePutListener(this,propKey,nextProp,transaction);} else if(LastProp){ delete listener(this,PropKey);} } .}//注册事件函数enqueuePutListener(inst,registrationName,Listener,transaction){ var container info=inst ._ nativecontainerinfordoc=container info ._ ownerDocument.//去文件上注册listenTo(注册名称,文档);//事务结束之后putListener事务。getreactmtmountready().入队(putListener,{ inst: inst,注册名:注册名,listener: listener,});}看下绑定方法

reactbrowserveventemitter。射流研究…

//注册名称:需要绑定的事件//当前成分所属的文件,即事件需要绑定的位置列表:函数(registrationName,contentDocumentHandle){ var mount at=contentDocumentHandle;//获取当前文档上已经绑定的事件var正在侦听=getListeningForDocument(挂载于);如果(.) { //冒泡处理ReactBrowserEventEmitter .reacteventlistener。trappbulletevent(.);} else if(.) { //捕捉处理ReactBrowserEventEmitter .reacteventlistener。trapccapturedevent(.);} .},走到最后其实就是doc.addEventLister(事件,回调,假);

可以看出所有事件绑定在文档上

所以事件触发的都是ReactEventListener的dispatchEvent方法

回调事件储存

listenerBank

反应维护了一个listenerBank的变量保存了所有的绑定事件的回调。

回到之前注册事件的方法

函数enqueuePutListener(inst,registrationName,Listener,transaction){ var container info=inst ._ nativecontainerinfordoc=container info ._ ownerDocumentif(!doc) { //服务器渲染。返回;} listenTo(registrationName,doc);transaction.getReactMountReady().入队(putListener,{ inst: inst,注册名:注册名,listener: listener,});}当绑定完成以后会执行putListener。

var listenerBank={ };var get dictionary key=function(inst){//inst为组建的实例化对象//_rootNodeID为组件的唯一标识返回"."本月的_ rootnode id } var EventPluginHub={//inst为组建的实例化对象//注册名称为事件名称//listner为我们写的回调函数,也就是列子中的this.autoFocus putListener:函数(inst,registrationName,listener){ 0.var key=get dictionary key(inst);var bank for registration name=listenerBank[注册名称]| |(listenerBank[注册名称]={ });银行注册名称[密钥]=侦听器;}}EventPluginHub在每个反应中只实例化一次。也就是说,项目组所有事件的回调都会储存在唯一的listenerBank中。

事件触发

注册事件流程图所示,所有的事件都是绑定在文档上。回调统一是ReactEventListener的派遣方法。由于冒泡机制,无论我们点击哪个多姆,最后都是由文档响应(因为其他数字正射影像图根本没有事件监听)。也即是说都会触发ReactEventListener.js里的派遣方法。

我们先看一下事件触发的流程图:

dispatchEvent:函数(topLevelType,nativeEvent) { if(!ReactEventListener ._ enabled){ return;} //这里得到TopLevelCallbackBookKeeping的实例对象,本例中第一次触发dispatchEvent时//簿记实例of toplevelback bookkeing/簿记=toplevelback bookkeing { topLevelType : ' topClick ',nativeEvent: 'click ',祖先3360 Array(0)} var簿记=toplevellbackbookkeing。getpoolid(TopLeveType,nativeEvent);尝试{ //同一周期正在处理的事件队列允许//`preventDefault ` .//接着执行handleTopLevelImpl(簿记)reatuptateds。batchedupdateds(handleTopLevelImpl,簿记);}最后{ //回收toplevellbackbookkeeping。释放(簿记);} }函数handleTopLevelImpl(簿记){ var nativeEventTarget=getEventTarget(簿记。nativeEventTarget//获取当前事件的虚拟数字正射影像图元素var targetInst=reactdomcomponentree。getclosestinstancefromnode(nativeveventarget);定义变量祖先=目标研究所做{簿记。祖先。推送(祖先);祖先=祖先find ParaMeter(祖先);} while(祖先);for(var I=0;我簿记。祖先。长度;i ) { targetInst=簿记。祖先[一];//这里的_handleTopLevel对应的就是ReactEventEmitterMixin.js里的handleTopLevelreatevenlistener ._handleTopLevel(簿记。顶级类型,目标安装,簿记。nativeEvent,getEventTarget(簿记。nativeevent));}}//这里的查找父项曾经给我带来误导,我以为去找当前元素所有的父节点,但其实不是的,//我们知道一般情况下,我们的组件最后会被包裹在div id='root'/div的标签里//一般是没有组件再去嵌套它的,所以通常返回null/** *查找最深的反应组件,该组件完全包含*传入实例的根(用于整个反应树相互嵌套*的情况)。如果反应树没有嵌套,则返回空值*/function FindParent(inst){ while(inst ._hostParent) { inst=inst ._ HostParent } var rootNode=reactdomComponentstree。getnodeforminstance(inst);定义变量容器=rootNode.parentNode返回reactdomtomcomponentree。getclosestinstancefromnode(容器);}我们看一下核心方法_handleTopLevel

ReactEventEmitterMixin.js

//这就是核心的处理了handleTopLevel:函数(topLevelType,targetInst,nativeEvent,nativeEventTarget) { //返回合成事件//这里进入了EventPluginHub,调用事件插件方法,返回合成事件,并执行队列里的dispatchtlistener var events=eventpluginhub。extracevents(topLevelType、targetInst、nativeEvent、nativeEventTarget);//执行合成事件runEventQueueInBatch(事件);}合成事件如何生成,请看上方事件触发的流程图runEventQueuelnBatch(事件)做了两件事

把dispatchListener里面的事件排队推进与线程分配执行eventpluginhub。processeventqueue(false);执行的细节如下:

EventPluginHub.js

//循环与线程分配调用var executeddispatcheandlespilevel=函数(e){ return executeddispatcheandless(e,false);};/* 从事件_dispatchListener .取出dispatchlistener,然后派遣事件, * 循环_dispatchListeners,调用执行调度*/var执行调度句柄=函数(事件,模拟){ if(事件){ //在这里派遣事件eventpluginutils。executeddispatchinorder(事件,模拟);//释放事件if(!事件。ispersistent()){ event。构造函数。发布(事件);} } };排队事件:函数(事件){ if(events){ event queue=accumulateInto(事件队列,events);} },/** *调度事件队列中的所有合成事件。* * @内部*/processEventQueue:函数(模拟){ //在处理之前将“事件队列”设置为null,这样我们就可以知道在处理时是否有更多//事件排队var处理事件队列=事件队列;eventQueue=nullif(模拟){ foreachaccupulated(正在处理事件队列,executeddispatcheandleslensimulated);} else { foreachaccublicated(正在处理事件队列,executedspatchedraletoplevel);} //如果费克斯抛出任何事件,这将是一个重新抛出的好时机重新接触。rethrowclaughterror();},/** *事件收集派单的标准/简单迭代*/function executeddispatchetinorder(事件,模拟的){ var dispatchListeners=event ._ dispatchListenersvar dispatchtinstance=事件. dispatchInstancesif(数组。isarray(dispatchListeners)){ for(var I=0;i dispatchListeners.lengthi ) { //由这里可以看出,合成事件的停止传播只能阻止反应合成事件的冒泡, //因为事件_dispatchListeners .只记录了由小艾绑定的绑定的事件,对于原生绑定的是没有记录的if(事件。ispropagationtop()){ break;} //侦听器和实例是两个总是同步的并行数组executeDispatch(事件,模拟,dispatchListeners[i],dispatchinstance[I]);} } else if(dispatchListeners){执行调度(事件,模拟,dispatchListeners,dispatchInstances }活动_ dispatchListeners=null事件_ dispatchInstances=null }函数executeDispatch(事件,模拟,侦听器,inst) { var type=event.type || '未知-事件';//注意这里将事件对应的数字正射影像图元素绑定到了当前目标上事件。CurrentTarget=EventPlugInutils。getnodeforminstance(inst);如果(模拟的){ reacherrutils。invokeeguardcallbackithcatch(类型、侦听器、事件);} else { //一般都是非模拟的情况,执行invokeeguardedcallback reaterrutils。invokeeguardedcallback(类型、侦听器、事件);} event.currentTarget=null}由上面的函数可知调度合成事件分为两个步骤:

通过_dispatchListeners里得到所有绑定的回调函数,在通过_ dispatchInstances的绑定回调函数的虚拟数字正射影像图元素循环执行_dispatchListeners里所有的回调函数,这里有一个特殊情况,也是反应阻止冒泡的原理其实在EventPluginHub.js里主要做了两件事情。

1.从事件_dispatchListener .取出dispatchlistener,然后派遣事件,循环_dispatchListeners,调用executeDispatch,然后走到重新接触。invokeeguardedcallback2.释放事件

上面这个函数最重要的功能就是将事件对应的数字正射影像图元素绑定到了当前目标上,

这样我们通过e.currentTarget就可以找到绑定事件的原生数字正射影像图元素。

下面就是整个执行过程的尾声了:

ReactErrorUtils.js

var fakeNode=document . create element(' react ');reacterrutils . invokeeguardedcallback=function(name,func,a,b) { var boundFunc=func.bind(null,a,b);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);};根据invokeGuardedCallback,最后react调用了伪造元素的dispatchEvent方法触发事件,触发后立即移除监听事件。

一般来说,分发点击事件的整个过程如下:

1.使用EventPluginHub生成复合事件。这里,请注意,对于同一事件类型,将只生成一个复合事件,并且同一事件类型的所有回调函数都存储在_ dispatchelisteners中。

2.按顺序执行

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:浅谈反应事件实现原理是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。