react.js事件机制node
一:在给input绑定事件的时候,很好奇为什么onChange的交互形式居然和onInput如出一辙。 由于原生的change事件是在input失去焦点的时候触发,但react的onChange则彻底不一样。 二:阻止事件冒泡,event.preventDeault()没办法阻止原生事件冒泡。
一 事件注册 二 事件合成 三 事件派发
1. 一切从createInstance,建立dom实例开始讲起。react
当react遍历tree建立真实dom实例的时候作了什么???数组
重点就在这几行代码。domElement等于真实建立的dom,里面调用的就是咱们熟悉的createElement。而precacheFiberNode和updateFiberProps两个方法分别给domElement(真实dom)添加了两个属性。全部react16项目中的dom都会拥有这两个属性,而且这个两个属性的属性名在同一个项目中是一致的。浏览器
图片描述dom
precacheFiberNode方法中 设置 node[internalInstanceKey] = 一个new FiberNode()的实例函数
updateFiberProps方法中 设置node[internalEventHandlersKey] = props 。这里的props就是<Component className='xxx' onClick={} /> 这里的属性。this
划重点了! 这两个方法等于将真实dom和fiber,props直接关联到了一块儿,相互引用,这点很重要,后续会用到,至关于前期准备。 spa
2. listenTo
createInstance后(仍然在fiber tree遍历中),程序兜兜转转,层层调用,最后终于走到了关键方法listenTo这里来,全部的事件注册逻辑都在这里实现。react作了一系列的兼容处理,尽量的保证各个浏览器端交互一致。code
ensureListeningTo里面调用的就listenTo。首先去遍历props中的属性。而registrationNameModules.hasOwnProperty(propKey),registrationNameModules是一个事件名的集合,几乎包含了全部的常见事件,这也就是若是你写一些稀奇古怪的事件,react是不识别的。若是断定props中的属性 如onClick在registrationNameModules中,而且值typeof === function,则会进入到listenTo中。对象
回到listenTo这个方法,他接收两个参数,一个是事件名如onClick,一个是contentDocumentHandle,一般就是document。
重点讲下这个 var dependencies = registrationNameDependencies[registrationName]; registrationNameDependencies这个东西理解为事件依赖。 就是说注册某个事件,react会强制依赖其余事件。而具体是哪些依赖,react的event模块已经帮咱们处理了,就不深层次探讨。
举例onChange事件就依赖了下面的一些事件
registrationNameDependencies = {
onChange = ['topBlur', 'topChange', 'topClick', 'topInput', 'topKey' ....还有]
}
这里react的事件都加上了top前缀,没什么太大的深层次含义,可能就是为了区分下吧,毕竟后续用到的时候,react会再次把它转回来的。如topInput转成input之类的。
接着往下走dependencies获得的是一个依赖事件数组,随即遍历这个数组,作一些hack处理,而后会调用这个方法trapBubbledEvent。
trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt) ,三个参数 dependency= topClick。 上面提到过的,会把top前缀什么的再次转回来, 因此topLevelTypes[dependency]就是click。mountAt就等于document(不考虑多个window)。那trapBubbledEvent又作了什么呢?
这里说白就是调用下面这个方法
熟悉了吧。这就是咱们常见的事件绑定了。。
又到划重点时间了
listenTo作的事情很简单,就是遍历props中的event,而后将事件和事件的依赖事件通通挂载到document上,而且全部的事件的回调函数走的都是dispatchEvent。
打个比方,若是我绑定一个onChange事件,那么react不只仅只绑定一个onChange事件到document上,还会绑定许多依赖事件上去,如focus,blur,input等等。是否是看出点什么苗头。onChange事件依赖了onInput事件,但这还并非 为何onChange的表现形式和onInput同样的 所有缘由。 这只是为后续的合成提供了依赖。
整个事件注册差很少就到这里为止了。react会把全部的事件都挂载到document上。这也是为何咱们event.stoppropagation()为何不能阻止原生冒泡。由于全部事件都是绑定在document上的。意味着你的原生事件都执行完了以后,才能执行document的事件。dispatchEvent会作统一的派发。能够说原生事件的执行顺序是早于react事件的。
稍微说起下react15的事件注册和16彻底不一样,16有一个listenBink的感念,因此的事件都注册到这个对象里面,而且由react_id关联起来。但16已经舍弃了react_id的感念。
以一个input输入框为例,当用户输入了数字1以后,react作了什么。
首先回到上面说的disPatchEvent中。全部的事件回调通通都是这个函数,如今咱们看看这个函数作了什么
dispatchEvent接收两个参数 topLevelType(事件topFocus之类的) nativeEvent就是原生的event对象。
dispatchEvent里面绕得有点深,跳来跳去。最后会走到下面这个方法里面来
handleTopLevel接收4个参数,targetInst就是fiber实例,上面提到过每一个dom中都会挂载两个属性,其中一个保存了fiber的引用。过程就是根据dispatchEvent获得的一个nativeEvent,能够获得
一个真正触发事件的nativeEventTarget元素(event.target),而后取得fiber引用便可。 这样handleTopLevel四个关键参数都齐全了。
handleTopLevel又作了什么呢。。根据里面方法的字面意思 无非是提取event对象(react的合成对象,并不是原生的event),而后放到事件队里去。
重点来讲说extractEvents方法,全部的奥秘都在这里了。。。它接收了handleTopLevel的四个参数。
这里可能有点绕,须要理解下plugins。简单的解释就是react的event模块所包含的eventPliguns。好像有6个左右的plugin吧,或许是不一样的组件处理不一样的事件类型吧。具体实现和功能就不必说了,太底层。
好比用户在input输入的过程,或许第一步是触发了某个元素的blur,而后是input的focus,而后是keydown,input之类等等,顺序就是按照浏览器的事件顺序。
咱们拿input事件举例,撇开其余无关事件(注:这里会解释 最开始提到的第一个问题)。
<input onChange= {this.changeText} /> 。
划重点了。。
上面的Input,虽然咱们只注册了一个onChange,但根据咱们前面的了解,react会注册依赖事件,onChange会依赖onInput,所以一样会注册onInput事件。
当input事件被触发的时候,遍历plugin去处理事件。并返回一个由plugin合成的event
events = accumulateInto(events, extractedEvents); 看这里,events是一个数组,accumulateInto等同于events.push(extractedEvents);
重点来 plugin处理onInput事件的时候,会生成两个event,一个是input,一个是change, 会生成两个event,一个是input,一个是change, 会生成两个event,一个是input,一个是change,
重要的事情说三遍,记住这个地方。后面是关键。
回到plugin这里来,进入到possiblePlugin.extractEvents里面去,这个函数返回一个event,而且在层层调用后执行了一个相当重要的函数traverseTwoPhase ,让咱们走进去看看这个函数
究竟作了什么?
traverseTwoPhase接收三个参数,inst = fiber实例 fn = accumulateDirectionalDispatches函数,等会会着重讲解这个方法。arg = event。这个event是possiblePlugin.extractEvents中生成的event对象,它把这个event当参数传递到更深层的方法里面,是为了在event上挂载两个极为重要的属性,等下细说。
traverseTwoPhase 具体作了什么呢?
while循环,取到fiber(触发事件的真正target所对应的虚拟dom)的全部父节点,等同于获得了一棵fiberTree。
以下
<ComponentA onClick={() => {console.log(1111)}}> <ComponentB onClick={() => {console.log(2222)}}> <ComponentC onClick={() => {console.log(3333)}} /> </ComponentB> </ComponentA>
假设有上面三个组件ComponentA, ComponentB, ComponentC。层层嵌套。那么path就等于[ComponentC, ComponentB, ComponentA];
traverseTwoPhase 中执行了两次循环,一次为captured捕获,即执行顺序从A-C。一次为bubbled冒泡,执行顺序为从C-A。这里咱们不考虑captured。详细讲解下冒泡过程。上面
说过了fn = accumulateDirectionalDispatches,咱们看看这里到底作了什么
重点看这里 var listener = listenerAtPhase(inst, event, phase); 这里就是取当前的dom有没有注册对应事件的listener。粗略解释下取得的过程就是利用了最上面说过的绑定在dom
上的props,若是listener存在,则将当前的listener和inst(理解为虚拟dom或者fiber实例)分别挂载到event下的两个数组里。 这里极其重要
注意啦。 accumulateDirectionalDispatches 这个函数是在 path的循环里执行的。回到上面的path等于[ComponentC, ComponentB, ComponentA]的例子。由于咱们的ABC三个组件都注册了对应的listener,故而event下的_dispatchListeners和_dispatchInstances都存储有其inst和listner。至此event算是合成完毕了,控制权回到possiblePlugin.extractEvents这里。
划重点 : 事件合成的过程。首先根据触发事件的target获得inst,而后遍历他的全部父节点(fiber.return属性),存储在局部遍历path中,记住这个path是有顺序关系的(后面能够解释react事件是如何阻止冒泡的)。获得path后进行遍历,假设遍历的组件一样注册了对应事件的listener,那么就挂载到event的_dispatchListeners和_dispatchInstances中去,这两个属性相当重要,后续的事件派发就是根据这两个属性进行的。 注意只有注册了对应事件的listener,才会挂载到event里面去。好比刚刚咱们的ABC都绑定了Click,天然都会push到_dispatchListeners中去。
回忆下上面刚刚说到的一个很绕的地方。 假设我给一个input绑定了onChange事件,那么react会绑定不少依赖事件到document上。其中就有input事件。 但当咱们触发input的时候,react是怎么触发到onChange的listener呢? 。重点就是刚刚说了三遍的地方,在合成input事件的时候,react会生成两个event,一个是input,一个是change,也就是说change的那个event挂载的_dispatchListeners里面存储了咱们的listener。后续派发的时候,会执行这个事件队列,对 队列里的event进行派发。 这也就很好的解释了为何咱们的change交互和input如出一辙。
事件合成以后就是事件派发了,虽然我分开来说,当两个过程是紧跟着的,合成事件后,全部的事件都会push到eventQueue里面去。派发的过程实际上就是遍历事件队列的过程。
遍历的过程一样有点绕,咱们只看关键的地方
有么有看到咱们熟悉的地方。_dispatchListeners和_dispatchInstances这两个里面保存了咱们的inst和listener。**方法对dispatchListeners进行了遍历,event.isPropagationStopped()
这个地方也就是咱们能够阻止合成事件冒泡的缘由呢。。** 由于dispatchListeners一样是按照冒泡的顺序插入的,就拿刚刚的ABC三个组件来讲。假设对B进行了阻止冒泡。那么A的onClick就没办法
执行了。
遍历过程当中拿到inst和对应的listener后,执行executeDispatch,后续的代码就简单直接了。
建立了个虚假的dom,绑定个自定义事件,而后再本身监听,再本身createEvent,再dispatch,触发callback,而后在callback里面触发真正的listener,同时会把合成的event传递进去。
最后在清空重置各类数据。。大结局了。
事件注册,事件合成,事件派发,三个阶段,这里就再也不总结了,每一阶段后都有个总结的。讲真react代码有点复杂,看不到的多看几遍吧。写的不对的地方指正下。有不懂的提问。。码这么多不容易了,可能有些单词拼错了,包容下。