zoukankan      html  css  js  c++  java
  • react16.0.0 事件系统源码解析

    原生事件系统
    监听真实DOM,我们想监听按钮的点击事件,那么我们在按钮DOM上绑定事件和对应的回调函数。JavaScript 事件最核心的包括事件监听 (addListener)、事件触发 (emit)、事件删除 (removeListener)。

    React事件系统
    React是将所有的事件都绑定在网页的document,通过统一的事件监听器处理并分发,找到对应的回调函数并执行。
     
    React为什么要自己实现一个事件系统?
    1 性能
    React作为一套View层面的框架,通过渲染得到vDOM(虚拟DOM),再由diff算法决定DOM树那些结点需要新增、替换或修改,假如直接在DOM结点插入原生事件监听,则会导致频繁的调用addEventListener(将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。)和removeEventListener(移除绑定事件),造成性能的浪费。所以React采用了事件代理的方法,对于大部分事件而言都在document上做监听,然后根据Event中的target来判断事件触发的结点。
    其次React合成的SyntheticEvent(它封装了浏览器原生事件对象,并对浏览器做了兼容。他和浏览器原生事件对象有相同的接口,包括stopPropagation()和preventDefault()。)采用了池的思想,从而达到节约内存,避免频繁的创建和销毁事件对象的目的。这也是如果我们需要异步使用一个syntheticEvent,需要执行event.persist()才能防止事件对象被释放的原因。(react合成了一个SyntheticEvent方法,达到了节约内存的效果)
    最后在React源码中随处可见batch做批量更新,基本上凡是可以批量处理的事情(最普遍的setState)React都会将中间过程保存起来,留到最后面flush(渲染,并最终提交到DOM树上)掉。就如浏览器对DOM树进行Style,Layout,Paint一样,都不会在操作ele.style.color='red';之后马上执行,只会将这些操作打包起来并最终在需要渲染的时候再做渲染。(react做了很多的批量操作,达到了性能优化)
    2 复用
    React看到在不同的浏览器和平台上,用户界面上的事件其实非常相似,例如普通的click,change等等。React希望通过封装一层事件系统,将不同平台的原生事件都封装成SyntheticEvent。(react封装的SyntheticEvent方法有很好的复用性。)
     
    事件注册
    1.判断事件名是否在react的事件名集合(registrationNameModules)中
    2.在registrationNameDependencies对象中获取当前事件的依赖事件(在listenTo方法中)
    3.绑定事件到document(在listen方法中)
    <button onClick={this.handleClick}></button>
    经由JSX解析,button会被当做组件挂载。而onClick这时候也只是一个普通的props。
     
    precacheFiberNode方法中 设置 node[internalInstanceKey] = 一个new FiberNode()的实例
    updateFiberProps方法中 设置node[internalEventHandlersKey] = props 。这里的props就是<button onClick={this.handleClick}></button /> 这里的属性。
    当react遍历tree创建真实dom实例的时候,里面调用的就是createElement。而precacheFiberNode和updateFiberProps两个方法分别给domElement,真实dom.添加了两个属性(fiber-虚拟节点,props)。所有react项目中的dom都会拥有这两个属性,并且这个两个属性的属性名在同一个项目中是一致的。
    这两个方法等于将真实dom和fiber,props直接关联到了一起,相互引用
    fiber tree遍历
    遍历当前dom节点的属性,如果其属性存在在registrationNameModules事件集合中,则执行。registrationNameModules事件集合(存储了React事件类型与浏览器原生事件类型映射的一个map),几乎包含了所有的常见事件,这也就是如果你写一些稀奇古怪的事件,react是不识别的。
    ReactEventListener:负责事件注册和事件分发。React将DOM事件全都注册到document这个节点上。
    topLevelType= topClick。
    topLevelTypes[topLevelType]就是click。
    trapBubbledEvent只是为了调用listen方法
    listen就是绑定事件到document(412 将指定的监听器注册到doument上,统一回调dispatchEvent方法)
    综述:listenTo就是遍历props中的event,然后将事件和事件的依赖事件统统挂载到document上,并且所有的事件的回调函数走的都是dispatchEvent。
    因为所有事件都是绑定在document上的。意味着你的原生事件都执行完了之后,才能执行document的事件。dispatchEvent会做统一的派发。可以说原生事件的执行顺序是早于react事件的。(冒泡)
     
    事件合成
    由于冒泡机制,无论我们点击哪个DOM,最后都是由document响应。也即是说都会触发dispatch
    在dispatch方法中我们会调用handleTopLevel方法
    extractedEvents第一步是根据原生事件合成合成事件,并且在vDOM上模拟捕获冒泡,收集所有需要执行的事件回调构成回调数组。第二步是遍历回调数组,触发回调函数。
    plugins是react的event模块所包含的eventPliguns插件
    events = accumulateInto(events, extractedEvents);events是一个数组,accumulateInto方法等同于events.push(extractedEvents);
     
    事件插件
    var DefaultEventPluginOrder=['ResponderEventPlugin', 'SimpleEventPlugin', 'TapEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];
    其中我们最常用到的就是SimpleEventPlugin。所以这里用SimpleEventPlugin来分析。
    extractEvents函数,用它生成一个合成事件,每个plugin都一定要有这个函数
    forEachAccumulated就是当event不是数组的时候,直接调用accumulateTwoPhaseDispatchesSingle,参数为events。
    参数
    accumulateDirectionalDispatches函数将所有绑定的同类型(所有onclick事件)的回调函数存放到事件的_dispatchListeners集合里面,将回调函数对应的虚拟dom元素按顺序存放到事件的_dispatchInstances集合中,
    listenerAtPhase方法 找到不同阶段(捕获/冒泡)元素绑定的回调函数
    getListener用于取出我们之前存放的回调函数
     
    事件分发
    1 将事件放进队列
    2 执行
     
    enqueueEvents方法就是在eventQueue里面Push events
    processEventQueue方法就是执行的过程(simulated==false),直接运行forEachAccumulated方法
    forEachAccumulated在之前说过,(判断processEventQueue是否是数组),然后执行
    executeDispatchesAndReleaseTopLevel方法 
    executeDispatchesInOrder方法通过_dispatchListeners里得到所有绑定的回调函数,再通过_dispatchInstances的绑定回调函数的虚拟dom元素。循环执行_dispatchListeners里所有的回调函数,这里有一个特殊情况,也是react阻止冒泡的原理
    executeDispatch函数就是将事件对应的dom元素绑定到了currentTarget上,这样我们通过e.currentTarget就可以找到绑定事件的原生dom元素。
    用这种方式初始化事件必须是由 Document.createEvent() 方法创建的实例. 本方法必须在事件被触发之前调用(用EventTarget.dispatchEvent()调用).事件 一旦被调用, 便不再做其他任何事.
     
     

  • 相关阅读:
    POJ 1328 Radar Installation
    POJ 1700 Crossing River
    POJ 1700 Crossing River
    poj 3253 Fence Repair (贪心,优先队列)
    poj 3253 Fence Repair (贪心,优先队列)
    poj 3069 Saruman's Army(贪心)
    poj 3069 Saruman's Army(贪心)
    Redis 笔记与总结2 String 类型和 Hash 类型
    数据分析方法有哪些_数据分析方法
    数据分析方法有哪些_数据分析方法
  • 原文地址:https://www.cnblogs.com/susu2020/p/12515777.html
Copyright © 2011-2022 走看看