前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React事件初探

React事件初探

作者头像
IMWeb前端团队
发布2019-12-04 14:48:37
7980
发布2019-12-04 14:48:37
举报
文章被收录于专栏:IMWeb前端团队

本文作者:IMWeb 朱灵子 原文出处:IMWeb社区 未经同意,禁止转载 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时的传播流程。

代码语言:javascript
复制
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事件系统框图
代码语言:javascript
复制
 * +------------+    .
 * |    DOM     |    .
 * +------------+    .
 *       |           .
 *       v           .
 * +------------+    .
 * | ReactEvent |    .
 * |  Listener  |    .
 * +------------+    .                         +-----------+
 *       |           .               +--------+|SimpleEvent|
 *       |           .               |         |Plugin     |
 * +-----|------+    .               v         +-----------+
 * |     |      |    .    +--------------+                    +------------+
 * |     +-----------.--->|EventPluginHub|                    |    Event   |
 * |            |    .    |              |     +-----------+  | Propagators|
 * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
 * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
 * |            |    .    |              |     +-----------+  |  utilities |
 * |     +-----------.--->|              |                    +------------+
 * |     |      |    .    +--------------+
 * +-----|------+    .                ^        +-----------+
 *       |           .                |        |Enter/Leave|
 *       +           .                +-------+|Plugin     |
 * +-------------+   .                         +-----------+
 * | application |   .
 * |-------------|   .
 * |             |   .
 * |             |   .
 * +-------------+   .

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

代码语言:javascript
复制
  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事件系统跨浏览器执行的部分代码实现:

代码语言:javascript
复制
    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;
      }
    }
  }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-06-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 顶级事件代理机制
  • 事件代理
  • React事件系统框图
  • React组件状态更新
  • React跨浏览器执行的实现原理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档