React事件初探

做者:朱灵子javascript

React 是一个 Facebook 和 Instagram 用来建立用户界面的 JavaScript 库。
创造 React 是为了解决一个问题:构建随着时间数据不断变化的大规模应用程序。
本文初探react的顶层事件代理机制~java

顶级事件代理机制

React采用的是顶层的事件代理机制,可以保持事件冒泡的一致性,能够跨浏览器执行,甚至能够在IE8中使用HTML5的事件。react

React 实现了一个“合成事件”层,这个事件层消除了 IE 与 W3C 标准实现之间的兼容问题。首先区分原生事件与合成事件,咱们在 componentDidMount 方法里面经过 addEventListener 绑定的事件就是浏览器原生事件,使用原生事件的时候注意在 componentWillUnmount 解除绑定 removeEventListener,全部经过 JSX 这种方式绑定的事件都是绑定到“合成事件”。web

“合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,而且在组件卸载(unmount)的时候自动销毁绑定的事件。浏览器

事件代理

在 DOM 节点上绑定事件比较消耗内存, React 则实现了一遍符合 W3C 规范的事件系统。接下来介绍该事件系统的实现原理, 事件 监听器被绑定到整个文档的根节点上。当事件被触发, 浏览器会给出一个触发目标事件的 DOM 节点。为了在 DOM 的层级传播事件, React 不会迭代 virtual DOM 的层级,而是依靠每一个 React component 各自独立的 id 来编码这个层级。咱们能经过简单的字符串操做来获取全部父级 component 的父级内容,再把事件监听存储在hashmap当中。下面的例子展现了事件广播到整个virtual DOM时的传播流程。app

clickCaptureListeners['a'](event); clickCaptureListeners['a.b'](event); clickCaptureListeners['a.b.c'](event); clickBubbleListeners['a.b.c'](event); clickBubbleListeners['a.b'](event); clickBubbleListeners['a'](event); 

浏览器为每一个事件和每一个listener建立一个新的事件对象,咱们能够从这个事件对象获取到事件的引用,可是这些事件对象也意味着高额的内存分配。为了减轻垃圾回收的负担,React 在启动时就为那些对象分配了一个内存池,当咱们须要用到某一个事件对象时就能够从这个内存池进行复用。函数

React事件系统框图

* +------------+    .
 * | DOM | . * +------------+ . * | . * v . * +------------+ . * | ReactEvent | . * | Listener | . * +------------+ . +-----------+ * | . +--------+|SimpleEvent| * | . | |Plugin | * +-----|------+ . v +-----------+ * | | | . +--------------+ +------------+ * | +-----------.--->|EventPluginHub| | Event | * | | . | | +-----------+ | Propagators| * | ReactEvent | . | | |TapEvent | |------------| * | Emitter | . | |<---+|Plugin | |other plugin| * | | . | | +-----------+ | utilities | * | +-----------.--->| | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |Enter/Leave| * + . +-------+|Plugin | * +-------------+ . +-----------+ * | application | . * |-------------| . * | | . * | | . * +-------------+ . 

框图中的ReactBrowserEventEmitter主要用于链接顶层事件侦听器,例如:编码

EventPluginHub.putListener(‘myID’, ‘onClick’, myFunction); 

接下来是对react事件系统原理框图的理解:spa

  • Top-level delegation用于捕获最原始的浏览器事件,它主要由ReactEventListener负责,ReactEventListener被注入后能够支持插件化的事件源,这一过程发生在主线程。
  • 咱们对各类事件进行去重复性处理以兼容不一样的浏览器,这一过程是由工做线程来完成的。
  • 最后咱们转发全部的本地事件到EventPluginHub(这些本地事件由相关顶级类型来捕获),EventPluginHub会注解每一个事件,而后分派事件。插件

    React组件状态更新

React中的props表明父级分发下来的属性,state表明组件内部能够自行管理的状态,而且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。子组件改变父组件state的办法只能是经过onClick等事件触发父组件声明好的回调,也就是父组件提早声明好函数或方法做为契约描述本身的state将如何变化,再将它一样做为属性交给子组件使用。

这样数据老是单向从顶层向下分发的,只有子组件回调在概念上能够回到state顶层影响数据,这样state必定程度上是响应式的。为了面临全部可能的扩展问题,最容易想到的办法就是把全部state集中放到全部组件顶层,而后分发给全部组件。

React跨浏览器执行的实现原理

React基于VirtualDom构建,能够更快、更有效地完成Dom操做。React实现了一套完整的事件合成机制,可以保持事件冒泡的一致性,同时能够实现跨浏览器执行,甚至能够在IE8中使用HTML5的事件。《Secrets of the JavaScript Ninja》中讲解了如何模拟 submit/focus/blur 等事件的冒泡,还讲述了mouseenter 与 mouseleave 等事件的模拟。除Firefox浏览器外均可使用支持冒泡的 focusin/focusout 来代替 focus/blur 事件,Firefox会在捕获阶段监听 focus/blur 事件。

submit/reset 事件会在鼠标点击或者按回车键时触发,因此能够监听冒泡的 click 和 keypress 事件,并判断触发事件的元素是否为一个 form 元素的后代节点,而后手动触发 submit/reset 事件。在Firefox v8.0浏览器下,若是做为top-level listener之一的onmousemove事件不是挂载在document元素上,那么当鼠标在不是该节点或者该节点所对应的子节点元素上移动时,onmousemove事件就不会被触发。根据不一样的浏览器对onmouseover事件、onscroll事件以及focusin、focusout事件的支持状况的不一样,react进行了有针对性的处理,如下为react事件系统跨浏览器执行的部分代码实现:

listenTo: function (registrationName, contentDocumentHandle) { var mountAt = contentDocumentHandle; var isListening = getListeningForDocument(mountAt); var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName]; var topLevelTypes = EventConstants.topLevelTypes; for (var i = 0; i < dependencies.length; i++) { var dependency = dependencies[i]; if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) { if (dependency === topLevelTypes.topWheel) { if (isEventSupported('wheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt); } else if (isEventSupported('mousewheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt); } else { // Firefox浏览器捕获鼠标滚动事件处理 ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt); } } else if (dependency === topLevelTypes.topScroll) { if (isEventSupported('scroll', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt); } else { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topScroll, 'scroll', ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE); } } else if (dependency === topLevelTypes.topFocus || dependency === topLevelTypes.topBlur) { if (isEventSupported('focus', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt); } else if (isEventSupported('focusin')) { // IE 浏览器支持的focusin和focusout事件 ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt); } // 保证blur和focus事件只监听一次 isListening[topLevelTypes.topBlur] = true; isListening[topLevelTypes.topFocus] = true; } else if (topEventMapping.hasOwnProperty(dependency)) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt); } isListening[dependency] = true; } } } 

原文连接:http://ivweb.io/topic/58227d0a0fea59e31b98bb5f

【腾讯云的1001种玩法】 征文活动技术文章等你来读! 点击查看详情

相关文章
相关标签/搜索