事件

合成事件与原生事件

合成事件

在 JSX 中直接绑定的事件,如

<a ref="aTag" onClick={(e)=>this.handleClick(e)}>UPDATE</a>

这里的 handleClick 事件就是合成事件。

VirtualDOM 在内存中是以 对象 的形式存在,React 基于 VirtualDOM 实现了一个 SyntheticEvent(合成事件)层,我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例(比如handleChange(reactEvent)),且与原生的浏览器事件有同样的接口。

原生事件

通过 JS 原生代码绑定的事件,如:

document.body.addEventListener('click',e => {
  // 通过e.target判断阻止冒泡
  if(e.target && e.target.matches('a')){
    return;
  }
  console.log('body');
})
// 或
this.refs.update.addEventListener('click',e => {
  console.log('update');
});

Q:为什么有时候还需要原生事件?

A:react 的 app 一般是挂在 body 下面某个div 结点上,如果我想将事件绑定在 body 上(比如监听 body 的滚动事件,window 的 resize 事件)就需要用原生事件。实际上,react 合成事件只是原生 DOM 事件的一个子集,它仅仅实现了 DOM Level 3 的事件接口,并且统一了浏览器的兼容问题,有些事件 React 并没有实现。

Q:在什么生命周期才可以绑定原生事件?

A:组件挂载完成之后,即 componentDidMount。

reactEvent 是封装好的事件,它是在 document 的回调里进行封装,并执行回调的。而原生的监听,在document 接收到冒泡时早就执行完了。reactEvent.nativeEvent.stopPropagation() 方法实际上是在最外层节点上调用了原生的 stopPropagation, 只阻止了 document 的冒泡。

合成事件的绑定

对于 合成事件 根据组件事件绑定的创建时间主要有两类方法:

方式一:render 时绑定

渲染时绑定主要有三种:

bind 显式绑定方式:

onChange = {this.handleChange.bind(this)}

但是在真正的开发场景中,由此引发的性能问题往往不值一提(除非是大型组件消费类应用或游戏)。

箭头函数隐式绑定:

这种方法其实和第一种类似,我们可以利用ES6 箭头函数 隐式 绑定 this:

onChange = {e => this.handleChange(e)}

双冒号隐式绑定:

ES next Stage-0Function bind syntax 提案:

onChange = {::this.handleChange}

函数绑定运算符是并排的两个双冒号( :: ),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即 this 对象),绑定到右边的函数上面。如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

Tips:babel 会将该方法转译成 .bind(this) 的方式。

方式二:创建实例时绑定

constructor 内绑定:

constructor 方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。 所以我们可以:

constructor(props) {
  super(props);
  this.handleChange = this.handleChange.bind(this);
}

就个人习惯而言,与前两种方法相比,constructor 内绑定在可读性和可维护性上也许有些欠缺。 同时,我们知道在 constructor 声明的方法不会存在实例的原型上,而属于实例本身的方法。每个实例都有同样一个 handleChange,这本身也是一种重复和浪费。

缺点:即使不用到state,也需要添加类构造函数来绑定this,代码量多; 添加参数要在构造函数中bind时指定,不在render中。

class 属性中使用 = 和箭头函数:

这个方法依赖于 ES next 的新特性,请参考:https://tc39.github.io/proposal-class-public-fields/

handleChange = () => {
  // call this function from render 
  // and this.whatever in here works fine.
};

总结一下这种方式的优点:

  • 使用箭头函数,有效绑定了 this;

  • 没有方式一的潜在性能问题;

  • 避免了constructor 内绑定的组件实例重复问题;

Last updated