这是 react 事件机制的第三节 - 事件注册,经过本文你将了解react 事件的注册过程,以及在这个过程当中主要通过了哪些关键步骤,同时结合源码进行验证和加强理解。前端
文章涉及到的源码是基于 react15.6.1版本,虽然不是最新版本可是也不会影响咱们对 react 事件机制的总体把握和理解。node
文中不会说很是细节的内容,而是会把大概的流程和原理性的内容进行介绍,作到对总体流程有个认知和理解。react
内容大纲数组
按照个人理解,react 事件注册过程其实主要作了2件事:缓存
a. 事件注册babel
b. 事件存储dom
a. 事件注册 - 组件挂载阶段,根据组件内的声明的事件类型-onclick,onchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent。函数
b. 事件存储 - 就是把 react 组件内的全部事件统一的存放到一个地方,也就是缓存起来,能够理解成放入一个对象内,为了在触发事件的时候能够查找到对应的方法去执行。this
再配个图spa
上面大体说了事件注册须要完成的两个目标,那完成目标的过程须要通过哪些关键处理呢?
首先 react 拿到将要挂载的组件的虚拟 dom(其实就是 react dom, 相似一个对象),而后处理react dom 的 props ,判断属性内是否有声明为事件的属性,好比onclick,这个时候获得事件类型 click 和对应的事件处理程序 fn,而后直行后面3步
a. 执行事件注册
b. 将react dom ,事件类型,处理函数 fn 放入数组存储
c. 组件挂载完成后,处理 b 步骤生成的数组,通过遍历把事件处理函数存储到listenerBank中
再配个图
3.1 得先从 jsx 提及
看个最熟悉的代码,也是咱们平常的写法
handleFatherClick=()=>{ } handleChildClick=()=>{ } render(){ return <div className="box"> <div className="father" onClick={this.handleFatherClick}> <div className="child" onClick={this.handleChildClick}>child </div> </div> </div> }
通过 babel 编译后,能够看到最终调用的方法是react.createElement,并且声明的事件类型和回调也是一个props。
react.createElement执行的结果会返回一个所谓的虚拟 dom(react element 或者 react dom),看下图
3. 2 开始处理props,拿到事件类型和回调 fn
ReactDOMComponent在进行组件加载(mountComponent)、更新(updateComponent)的时候,须要对props进行处理(_updateDOMProperties):
能够看下 registrationNameModules 的内容,就不细说了。
3.3 注册事件和事件的存储
【注册事件】
接着上面的代码执行到了这个方法
enqueuePutListener(this, propKey, nextProp, transaction);
在这个方法里会进行事件的注册以及事件的存储,包括冒泡和捕获的处理
根据当前的组件实例获取获取到最高父级-也就是document,而后执行方法 listenTo - 也是最关键的一个方法,进行事件绑定处理
源码文件:ReactBrowerEventEmitter.js
最后执行EventListener.listen(冒泡)或者EventListener.capture(捕获),
单看下冒泡的注册,其实就是addEventListener的第三个参数是 false
也能够看到注册事件的时候也对 ie 作了兼容。
上面没有看到 dispatchEvent 的定义,下面能够看到传入 dispatchEvent 方法的代码。
到这里事件注册就完事儿了。
【事件存储】
下一步开始事件的存储,在 react 里全部事件的触发都是经过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。
【事件存储结论】
react 把全部的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),而后在事件触发的时候去根据当前的组件id和事件类型查找到对应的事件。
再加个简易图
看源码:
function enqueuePutListener(inst, registrationName, listener, transaction) { var containerInfo = inst._hostContainerInfo; var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE; var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument; listenTo(registrationName, doc);//这个方法上面已说完 //这里涉及到了事务,事物会在之后的章节再介绍,主要看事件注册 //下面的代码是将putListener放入数组,当组件挂载完后会依次执行数组的回调。也就是putListener会依次执行 transaction.getReactMountReady().enqueue(putListener, { inst: inst,//组件实例 registrationName: registrationName,//事件类型 click listener: listener //事件回调 fn }); } function putListener() { var listenerToPut = this; //放入数组,回调队列 EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener); }
大体的流程就是执行完listenTo(事件注册),而后执行 putListener 方法进行事件存储,全部的事件都会存储到一个对象中 - listenerBank,具体由EventPluginHub进行管理。
//拿到组件惟一标识 id var getDictionaryKey = function getDictionaryKey(inst) { return '.' + inst._rootNodeID; } putListener: function putListener(inst, registrationName, listener) { //获得组件 id var key = getDictionaryKey(inst); //获得listenerBank对象中指定事件类型的对象 var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {}); //存储回调 fn bankForRegistrationName[key] = listener; //.... }
listenerBank其实就是一个二级 map,这样的结构更方便事件的查找。
这里的组件 id 就是组件的惟一标识,而后和fn 进行关联,在触发阶段就能够找到相关的事件回调。
看下listenerBank结构:
看到这个结构是否是很熟悉呢?就是咱们日常使用的 object.
到这里大体的流程已经说完,是否是感受有点明白又不大明白。
不要紧,再来个详细的图,从新理解下
本文主要是从总体流程上介绍了下 react 事件中事件的注册过程,并无深刻到源码的细节,有兴趣的小伙儿能够自查下源码,也但愿本文可以带给你一些启发,若文章有表述不清或有问题的地方欢迎留言交流。
更多精彩内容欢迎关注个人公众号-前端张大胖