zoukankan      html  css  js  c++  java
  • React事件初探

    作者:朱灵子

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

  • 相关阅读:
    发布全文检索类库外包
    给即将面试的人
    实验四 Web服务器2
    电子公文传输系统验收2功能测试
    整数范围与类型转换
    实验三电子公文传输系统1个人贡献
    socket测试
    电子公文传输系统验收4开发基础
    Web服务器1socket编程
    算法测试(课上测试)
  • 原文地址:https://www.cnblogs.com/libin-1/p/6801568.html
Copyright © 2011-2022 走看看