作者:朱灵子

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

顶级事件代理机制

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

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

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

事件代理

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

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事件系统原理框图的理解:

  • 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种玩法】 征文活动技术文章等你来读! 点击查看详情

React事件初探的更多相关文章

  1. javascript:自定义事件初探

    javascript:自定义事件初探   http://www.cnblogs.com/jeffwongishandsome/archive/2008/10/27/1317148.html

  2. React事件杂记及源码分析

    前提 最近通过阅读React官方文档的事件模块,发现了其主要提到了以下三个点  调用方法时需要手动绑定this  React事件是一种合成事件SyntheticEvent,什么是合成事件?  事件属性 ...

  3. react事件机制

    1. react的事件是合成事件((Synethic event),不是原生事件 <button onClick={this.handleClick}></button> &l ...

  4. 【React】354- 一文吃透 React 事件机制原理

    大纲 主要分为4大块儿,主要是结合源码对 react事件机制的原理 进行分析,希望可以让你对 react事件机制有更清晰的认识和理解. 当然肯定会存在一些表述不清或者理解不够标准的地方,还请各位大神. ...

  5. React 事件对象、键盘事件、表单事件、ref获取dom节点、react实现类似Vue双向数据绑定

    1.案例实现代码 import React, { Component } from 'react'; /** * 事件对象.键盘事件.表单事件.ref获取dom节点.react实现类似Vue双向数据绑 ...

  6. 源码看React 事件机制

    对React熟悉的同学都知道,React中的事件机制并不是原生的那一套,事件没有绑定在原生DOM上,发出的事件也是对原生事件的包装.那么这一切是怎么实现的呢? 事件注册 首先还是看我们熟悉的代码 &l ...

  7. React事件绑定的几种方式对比

    React事件绑定 由于类的方法默认不会绑定this,因此在调用的时候如果忘记绑定,this的值将会是undefined.通常如果不是直接调用,应该为方法绑定this.绑定方式有以下几种: 1. 在构 ...

  8. React Native初探

    前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...

  9. React事件属性

    一.简介 二.滚动例子,滚动改变颜色 <!DOCTYPE html> <html lang="zh-cn"> <head> <meta c ...

随机推荐

  1. ifconfig与内核通信 ifreq 结构体分析和使用

    结构原型: /* * Interface request structure used for socket * ioctl's.  All interface ioctl's must have p ...

  2. SVN文件上感叹号、加号、问号等图标的原因

    黄色感叹号(有冲突): --这是有冲突了,冲突就是说你对某个文件进行了修改,别人也对这个文件进行了修改,别人抢在你提交之前先提交了,这时你再提交就会被提示发生冲突,而不允许你提交,防止你的提交覆盖了别 ...

  3. Characters_of_the_Three_Kingdoms - 三国人物结构化数据

    Characters_of_the_Three_Kingdoms - 三国人物结构化数据 三国人物结构化数据 为什么会有这个项目 需求1:摆脱网上那些长篇累牍的文章: 需求2:只是想简单查看下人物姓甚 ...

  4. html5本次存储几种方式

    一.cookies 大家都懂的,不必多说 二.sessionStorage/localStorage HTML5 LocalStorage 本地存储 说到本地存储,这玩意真是历尽千辛万苦才走到HTML ...

  5. 定期删除30天以前的elasticsearch的日志

    脚本/scripts/delete-elk.log #!/bin/bash DATE=`date -d "1 days ago" +%Y.%m.%d` ip=`ifconfig e ...

  6. mysql 事务回滚

    begindeclare t_error integer default 0;  //添加变量t_error并赋初始值为0declare continue handler for sqlexcepti ...

  7. SQL1:基础

    1.SQL命令类型: 1)DDL:CREATE TABLE/INDEX/VIEW ; ALTER TABLE/INDEX/VIEW ; DROP TABLE/INDEX 2)DML:INSERT,UP ...

  8. Java常用工具类之删除文件

    package com.wazn.learn.util; import java.io.File; /** * 删除文件工具类 * @author yangzhenyu * */ public cla ...

  9. java反射,代码优化

    java的反射机制属实强大,能解决好些问题 在接手别人写的代码的时候,有一个bean类的get方法特别low,我都看不下去 重复代码写五遍,我都觉得太不合理.之后将其中代码抽取出来修改了下. publ ...

  10. hdu 1690 Bus System (有点恶心)

    Problem Description Because of the huge population of China, public transportation is very important ...