【React深刻】React事件机制

关于React事件的疑问

  • 1.为何要手动绑定this
  • 2.React事件和原生事件有什么区别
  • 3.React事件和原生事件的执行顺序,能够混用吗
  • 4.React事件如何解决跨浏览器兼容
  • 5.什么是合成事件

下面是我阅读过源码后,将全部的执行流程总结出来的流程图,不会贴代码,若是你想阅读代码看看具体是如何实现的,能够根据流程图去源码里寻找。node

事件注册

image

  • 组件装载 / 更新。
  • 经过lastPropsnextProps判断是否新增、删除事件分别调用事件注册、卸载方法。
  • 调用EventPluginHubenqueuePutListener进行事件存储
  • 获取document对象。
  • 根据事件名称(如onClickonCaptureClick)判断是进行冒泡仍是捕获。
  • 判断是否存在addEventListener方法,不然使用attachEvent(兼容IE)。
  • document注册原生事件回调为dispatchEvent(统一的事件分发机制)。

事件存储

image

  • EventPluginHub负责管理React合成事件的callback,它将callback存储在listenerBank中,另外还存储了负责合成事件的Plugin
  • EventPluginHubputListener方法是向存储容器中增长一个listener。
  • 获取绑定事件的元素的惟一标识key
  • callback根据事件类型,元素的惟一标识key存储在listenerBank中。
  • listenerBank的结构是:listenerBank[registrationName][key]

例如:react

{
    onClick:{
        nodeid1:()=>{...}
        nodeid2:()=>{...}
    },
    onChange:{
        nodeid3:()=>{...}
        nodeid4:()=>{...}
    }
}

事件触发 / 执行

image

这里的事件执行利用了React的批处理机制,在前一篇的【React深刻】setState执行机制中已经分析过,这里再也不多加分析。segmentfault

  • 触发document注册原生事件的回调dispatchEvent
  • 获取到触发这个事件最深一级的元素

例以下面的代码:首先会获取到this.child浏览器

<div onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div onClick={this.childClick} ref={ref => this.child = ref}>
          test
        </div>
      </div>
  • 遍历这个元素的全部父元素,依次对每一级元素进行处理。
  • 构造合成事件。
  • 将每一级的合成事件存储在eventQueue事件队列中。
  • 遍历eventQueue
  • 经过isPropagationStopped判断当前事件是否执行了阻止冒泡方法。
  • 若是阻止了冒泡,中止遍历,不然经过executeDispatch执行合成事件。
  • 释放处理完成的事件。

react在本身的合成事件中重写了stopPropagation方法,将isPropagationStopped设置为true,而后在遍历每一级事件的过程当中根据此遍历判断是否继续执行。这就是react本身实现的冒泡机制。babel

合成事件

image

  • 调用EventPluginHubextractEvents方法。
  • 循环全部类型的EventPlugin(用来处理不一样事件的工具方法)。
  • 在每一个EventPlugin中根据不一样的事件类型,返回不一样的事件池。
  • 在事件池中取出合成事件,若是事件池是空的,那么建立一个新的。
  • 根据元素nodeid(惟一标识key)和事件类型从listenerBink中取出回调函数
  • 返回带有合成事件参数的回调函数

总流程

将上面的四个流程串联起来。dom

image

为何要手动绑定this

经过事件触发过程的分析,dispatchEvent调用了invokeGuardedCallback方法。函数

function invokeGuardedCallback(name, func, a) {
  try {
    func(a);
  } catch (x) {
    if (caughtError === null) {
      caughtError = x;
    }
  }
}

可见,回调函数是直接调用调用的,并无指定调用的组件,因此不进行手动绑定的状况下直接获取到的thisundefined工具

这里可使用实验性的属性初始化语法 ,也就是直接在组件声明箭头函数。箭头函数不会建立本身的this,它只会从本身的做用域链的上一层继承this。所以这样咱们在React事件中获取到的就是组件自己了。this

和原生事件有什么区别

  • React 事件使用驼峰命名,而不是所有小写。
  • 经过 JSX , 你传递一个函数做为事件处理程序,而不是一个字符串。

例如,HTMLspa

<button onclick="activateLasers()">
  Activate Lasers
</button>

React 中略有不一样:

<button onClick={activateLasers}>
  Activate Lasers
</button>

另外一个区别是,在 React 中你不能经过返回 false 来阻止默认行为。必须明确调用 preventDefault

由上面执行机制咱们能够得出:React本身实现了一套事件机制,本身模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法,而且抹平了各个浏览器的兼容性问题。

React事件和原生事件的执行顺序

componentDidMount() {
    this.parent.addEventListener('click', (e) => {
      console.log('dom parent');
    })
    this.child.addEventListener('click', (e) => {
      console.log('dom child');
    })
    document.addEventListener('click', (e) => {
      console.log('document');
    })
  }

  childClick = (e) => {
    console.log('react child');
  }

  parentClick = (e) => {
    console.log('react parent');
  }

  render() {
    return (
      <div onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div onClick={this.childClick} ref={ref => this.child = ref}>
          test
        </div>
      </div>)
  }

执行结果:

image

由上面的流程咱们能够理解:

  • react的全部事件都挂载在document
  • 当真实dom触发后冒泡到document后才会对react事件进行处理
  • 因此原生的事件会先执行
  • 而后执行react合成事件
  • 最后执行真正在document上挂载的事件

react事件和原生事件能够混用吗?

react事件和原生事件最好不要混用。

原生事件中若是执行了stopPropagation方法,则会致使其余react事件失效。由于全部元素的事件将没法冒泡到document上。

由上面的执行机制不可贵出,全部的react事件都将没法被注册。

合成事件、浏览器兼容

function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
这里, e 是一个合成的事件。 React 根据 W3C 规范 定义了这个合成事件,因此你不须要担忧跨浏览器的兼容性问题。

事件处理程序将传递 SyntheticEvent 的实例,这是一个跨浏览器原生事件包装器。 它具备与浏览器原生事件相同的接口,包括 stopPropagation()preventDefault() ,在全部浏览器中他们工做方式都相同。

每一个 SyntheticEvent 对象都具备如下属性:

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

React合成的SyntheticEvent采用了事件池,这样作能够大大节省内存,而不会频繁的建立和销毁事件对象。

另外,无论在什么浏览器环境下,浏览器会将该事件类型统一建立为合成事件,从而达到了浏览器兼容的目的。

推荐阅读

【React深刻】setState的执行机制

相关文章
相关标签/搜索