Skip to content

阅读前的测试 ​

本节目标:理解“阅读前的测试”的核心思路,并能把它用于实际开发或面试表达。 先看以下问题:

  1. 点击button,最后的输出顺序是什么?
  2. B,E,F 处的type都是啥?
export default class Test extends React.Component {
  componentDidMount() {
    document.querySelector("#btn").addEventListener("click", (e) => {
      console.log("A inner listener");
      setTimeout(() => {
        console.log("B inner listener timer", e.type);
      });
    });
    document.body.addEventListener("click", (e) => {
      console.log("C document listener");
    });
    window.addEventListener("click", (e) => {
      console.log("D window listener");
    });
  }
  outClick(e) {
    setTimeout(() => {
      console.log("E out timer", e.type);
    });
    console.log("F out e", e.type);
  }
  innerClick = (e) => {
    console.log("G inner e");
    e.stopPropagation();
  };
  render() {
    return (
      <div onClick={this.outClick}>
        <button id="btn" onClick={this.innerClick}>

点我

        </button>
      </div>
    );
  }
}
1.

最后的输出顺序为

 A C G F D B E 2. B,F

处的type为click,而E处的type为null 如果错了,不要紧~~咱们往下看吧。

  • 如果元素有原生事件处理器,响应之;
  • 对于元素上的合成事件,则将其绑定在了document上;
  • 事件向外冒泡,父级元素如果有原生事件,则响应之,没有则将父元素上的合成事件绑定到document之上;
  • 事件到了documnet之后,先响应document的原生事件处理器,再依次响应由里到外的合成事件;
  • 事件冒泡到window上,先响应原生的,window无法设置react合成事件;

合成事件(SyntheticEvent) 考虑到浏览器的兼容性和性能问题,react拥有自己的事件系统,即合成事件。

  • react的合成事件不会直接绑定到目标dom节点上,而是利用事件委托绑定到最外层document上;
  • 用一个统一的监听器去监听,这个监听器上保存着目标节点与事件对象的映射;
  • 当你挂载或删除节点时,所对应的事件就会从映射表上绑定或删除。

响应过程(对应第一问) 我们参照上题,详细说一下事件的响应过程:

  1. 由于我们写的几个监听事件addEventListener,都没有给第三个参数,默认值为false,所以在事件捕获阶段,原生的监听事件没有响应,react合成事件没有实现事件捕获。所以在捕获阶段没有事件响应。
  2. 接着到了事件绑定的阶段,button上挂载了原生事件,于是输出"A",setTimeout中的"B"则进入EVENT LOOP。在上一段中,我们提到react的合成事件是挂载到document上,所以“G”没有输出。
  3. 之后进入冒泡阶段,到了div上,与上条同理,不会响应outClick,继续向上冒泡。
  4. 之后冒泡到了document上,先响应挂载到document的原生事件,输出"c"。之后接着由里向外响应合成事件队列,即输出"G""F",将'E'放入EVENT LOOP。再向上冒泡。
  5. 到了window上,响应‘D’,之后再处理EVENT LOOP上的事件,输出'B' 'E'.

合成事件对象react合成事件是原生事件的扩充,它符合w3c标准。下面是合成事件对象的属性:

属性名类型描述
bubblesboolean事件是否可冒泡
cancelableboolean事件是否可拥有取消的默认动作
currentTargetDOMEventTarget事件监听器触发该事件的元素(绑定事件的元素)
defaultPreventedboolean当前事件是否调用了 event.preventDefault()方法
eventPhasenumber事件传播的所处阶段[0:Event.NONE-没有事件被处理,1:Event.CAPTURING_PHASE - 捕获阶段,2:被目标元素处理,3:冒泡阶段(Event.bubbles为true时才会发生)]
isTrustedboolean触发是否来自于用户行为,false为脚本触发
nativeEventDOMEvent浏览器原生事件
preventDefault()void
isDefaultPrevented()boolean返回的事件对象上是否调用了preventDefault()方法
stopPropagation()void
isPropagationStopped()boolean返回的事件对象上是否调用了stopPropagation()方法
targetDOMEventTarget触发事件的元素
timeStampnumber事件生成的日期和时间
typestring当前 Event 对象表示的事件的名称,是注册事件的句柄,如,click、mouseover...etc.

注意preventDefault()和stopPropagation(),他们只能对react合成事件产生作用,尤其 注意stopPropagation()浏览器原生事件是不会被禁止传播的。比如我们把题目中的注释去掉,会发现“C”依然被输出,原因就在于合成事件其实是通过document上的监听器进行事件委托的(看不懂的同学接着看上一小节。) 事件池(对应第二问) 在react中,合成事件被调用后,合成事件对象会被重用,所有属性被置为null,所以题目中outClick中通过异步方式访问e.type是取不到任何值的,如果需要保留属性,可以调用event.persist()事件,会保留引用。 彩蛋:如果对这里好奇的同学可以在控制台打印一下event,会发现不论是不是异步方法中的event,它的所有属性都是null。但为什么会得到它的属性值呢,大概是因为get方法吧,具体的原因我也没深入。如果了解原理的同学请在评论区留言~~~或者等我之后填坑 源码构成 react事件系统的核心文件renderers/shared/event/EventPluginHub.js,感兴趣的同学可以去看看源码

~~
var EventPluginHub = { injection, putListener, getListener, deleteListener, deleteAllListeners, extractEvents, //

当顶层事件被触发,该方法中会传入原生事件,生成合成事件

 enqueueEvents,//

合成事件进入事件队列

 processEventQueue, //

调度事件队列上的所有合成事件

}

参考文档

  1.  React

    事件系统和源码浅析

  2.  react

    事件系统源码

  3.  react

    官方文档 -- SyntheticEvent > 来自

 <https://github.com/mytac/blogs/blob/master/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/react/react%E4%BA%8B%E4%BB%B6%E7%B3%BB%E7%BB%9F.md>

本站总访问 — 次