玩转 React(六)- 处理事件

前面的文章介绍了 React 的 JSX 语法、组件的建立方式、组件的属性、组件的内部状态以及组件的生命周期。另外,还顺带说了各个知识点要重点注意的事情,以及我在项目实践中的一些经验。若是你以为对本身有帮助,能够经过 玩转 React(一)- 前言 中的文章目录进行阅读。javascript

另外,为了方便你们更好地交流 React、分享前端开发经验,我建了一个微信群,因为微信群二维码有时间限制,你能够先加我好友(个人微信:leobaba88),验证信息 玩转 React,我会拉你入群,欢迎你们,下面是个人微信二维码。html

好的,言归正传,今天咱们说一下在 React 中是如何处理事件的。事件处理是前端开发过程当中很是重要的一部分,经过事件处理机制,咱们的前端应用能够响应用户的各类操做,从而实现一个富交互的前端应用。前端

内容摘要

  • 如何为 React 的内置组件设置事件处理函数。
  • React 事件对象与浏览器原生 DOM 事件对象的区别。
  • 默认状况下不能以异步的方式使用事件对象,如在 setTimeout 中。
  • 不要在组件中使用 addEventListener 注册事件处理函数,有坑。
  • 绑定事件处理函数 this 指向的四中方式以及他们的优缺点。

React 内置组件的事件处理

我所说的 React 内置组件是指 React 中已经定义好的,能够直接使用的如 div、button、input 等与原生 HTML 标签对应的组件。java

咱们先回顾一下浏览器原生 DOM 上注册事件的方式。react

第一种方式git

<a href="#" onclick="console.info('You clicked me.'); return false;">
    Click me.
</a>复制代码

这是一种古老的方式,在 DOM level 1 规范中的事件注册方式,如今已经不多使用了。github

这种方式,用来注册事件的 HTML 属性的值是一个字符串,是一段须要执行的 JavaScript 代码。segmentfault

能够经过 return false; 来阻止当前 HMTL 元素的默认行为,如 a 标签的页面跳转。浏览器

关于 DOM 规范的级别能够参考:DOM Levelsbash

第二种方式:

<a href="#" id="my-link">
    Click me.
</a>

<script type="text/javascript">
    document.querySelector('#my-link').addEventListener('click', (e) => {
        e.preventDefault();
        console.info("You clicked me.");
    });
</script>复制代码

这是 DOM level 2 规范中引入的事件注册方式,目前各浏览器也支持的很好,用得是最多的,就是写起来有点啰嗦哈。

在 React 中,事件注册与方式一很是相似,不过有以下几点不一样:

  • 属性名称采用驼峰式(如:onClick,onKeyDown),而不是全小写字母。
  • 属性值接受一个函数,而不是字符串。
  • return false; 不会阻止组件的默认行为,须要调用 e.preventDefault();

以下所示:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}复制代码

这是一个以函数方式定义的组件,组件渲染一个 a 元素,设置l连接的点击事件,经过事件处理函数接收到的事件对象(e),阻止了连接的默认行为,并打印 "The link was clicked." 到控制台上。设置 React 内置组件的事件处理函数是否是很是简单。

React 事件对象 VS 原生的 DOM 事件对象

React 中的事件对象称之为 SyntheticEvent(合成对象),它是依据 DOM Level 3 的事件规范实现的,这样作最大的好处是能够屏蔽浏览器的差别,各类厂商的浏览器对规范的实现程度是不同的,若是直接使用原生 DOM 事件对象的话,有些状况下你须要考虑浏览器的兼容性。而 React 经过 SyntheticEvent 已经把这些杂事帮你搞定了,在任何 React 支持的浏览器下,事件对象都有一致的接口。

React 中全部的事件处理函数都会接收到一个 SyntheticEvent 的实例 e 做为参数,若是在某些特殊的场景中,你须要用到原生的 DOM 事件对象,能够经过 e.nativeEvent 来获取。

不要在异步过程当中使用 React 事件对象

须要说明的是,出于性能的考虑,React 并非为每个事件处理函数生成一个全新的事件对象,事件对象会被复用,当事件处理函数被执行之后,事件对象的全部属性会被设置为 null,因此在事件处理函数中,你不能以异步的方式使用 React 的事件对象,由于那时候事件对象的全部属性都是 null 了,或者已经不是你关心的那个事件了。

尽可能不要使用 addEventListener

这里稍微深刻一下,否则我怕有的同窗会踩坑。React 内部本身实现了一套高效的事件机制,为了提升框架的性能,React 经过 DOM 事件冒泡,只在 document 节点上注册原生的 DOM 事件,React 内部本身管理全部组件的事件处理函数,以及事件的冒泡、捕获。

因此说,若是你经过 addEventListener 注册了某个 DOM 节点的某事件处理函数,而且经过 e.stopPropagation(); 阻断了事件的冒泡,那么该节点下的全部节点上,同类型的 React 事件处理函数都会失效。

以下示例,虽然设置的连接的点击事件,可是它却执行不了。

class CounterLink extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
    this.handleClick = this.handleClick.bind(this);
  }
  componentDidMount() {
    document.querySelector('.my-link').addEventListener('click', (e) => {
      console.info('raw click');
      e.stopPropagation();
    })
  }
  handleClick(e) {
    e.preventDefault();
    console.info('react click');
    this.setState(preState => ({ count: preState.count + 1 }));
  }
  render() {
    return (
      <div className="my-link">
        <a href="#" onClick={this.handleClick}>Clicked me {this.state.count} times.</a>    
      </div>
    )
  }
}
ReactDOM.render(<CounterLink/>, document.querySelector("#root"));复制代码

codepen.io/Sarike/pen/…

如何绑定事件处理函数的 this

在以类继承的方式定义的组件中,为了能方便地调用当前组件的其余成员方法或属性(如:this.state),一般须要将事件处理函数运行时的 this 指向当前组件实例。

以下面的示例:

class Link extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  handleClick(e) {
    e.preventDefault();
    this.setState(preState => ({ count: preState.count + 1 }));
  }
  render() {
    return <a href="#" onClick={this.handleClick}>Clicked me {this.state.count} times.</a>    
  }

}

ReactDOM.render(<Link/>, document.querySelector("#root"))复制代码

当点击连接时,控制台会报错:Uncaught TypeError: Cannot read property 'setState' of undefined,就是由于没有将 handleClick 运行时的 this 绑定到当前组件。

绑定事件处理函数的 this 到当前组件,有以下几种方式。

第一种方式,经过 bind 方法,原地绑定事件处理函数的 this 指向,以下所示:

<a href="#" onClick={this.handleClick.bind(this)}>
    Clicked me {this.state.count} times.
</a>复制代码

这种方式的优势是书写起来相对简单,可是每次渲染都会执行 bind 方法生成一个新的函数,会有额外的开销,因为事件处理函数是做为属性传递的,因此从而致使子组件进行从新渲染,显然这不是一种好的方式。

第二种方式,经过一个箭头函数将真实的事件处理函数包装一下,以下所示:

<a href="#" onClick={e => this.handleClick(e)}>
    Clicked me {this.state.count} times.
</a>复制代码

这种方式书写起来也不算麻烦,不过也没有解决第一种方式面临的性能开销和从新渲染的问题。可是这种方式的一个好处是能清晰描述事件处理函数接收的参数列表(这一点可能因人而异,我的观点以为这是一个优势)。

第三种方式,在 constructor 中预先将全部的事件处理函数经过 bind 方法进行绑定。以下所示:

class Link extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }

    // 重点在这里
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(e) {
    e.preventDefault();
    this.setState(preState => ({ count: preState.count + 1 }));
  }
  render() {
    return <a href="#" onClick={this.handleClick}>Clicked me {this.state.count} times.</a>    
  }
}

ReactDOM.render(<Link/>, document.querySelector("#root"))复制代码

这种方式能解决前两种方式面临的额外开销和从新渲染的问题,可是写起来略微有点复杂,由于一个事件处理函数要分别在三个不一样的地方进行定义、绑定 this 和使用。

第四种方式,使用类的成员字段定义语法,以下所示:

class Link extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  handleClick = e => {
    e.preventDefault();
    this.setState(preState => ({ count: preState.count + 1 }));
  }
  render() {
    return <a href="#" onClick={this.handleClick}>Clicked me {this.state.count} times.</a>    
  }
}
ReactDOM.render(<Link/>, document.querySelector("#root"))复制代码

这种方式解决了上面三种方式面临的性能开销、从新渲染以及书写麻烦的问题。惟一的问题就是这种语法目前处于 Stage 3,还未归入到正式的 ES 规范中。参考:github.com/tc39/propos…

不过这也没太大关系。

总结

本文的内容并很少,可能说的有点啰嗦。简单总结一下,React 中经过设置组件的 事件属性 来注册事件,React 内部本身实现了一套包含冒泡、捕获逻辑在内的事件机制,因此尽可能不要使用 addEventListener,除非你知道本身在干什么。有四种为事件处理函数绑定 this 的方法,推荐使用类属性定义的方式来定义处理函数,若是你不太在乎哪一点性能开销的话,可使用箭头函数包装真实事件回调的方式。另外,事件对象在 React 中是被复用的,事件回调被执行之后,事件对象的全部属性会被重置为 null,因此不要在异步的过程当中使用事件对象。

好了,有什么疑问能够加微信群交流,个人微信号:leobaba88,验证信息:玩转 React。


PS:本系列的全部文章将在 segmentfault 和 掘金 同步发布。

本做品保留全部权利。未得到许可人许可前,不容许他人复制、发行、展览和表演做品。不容许他人基于该做品创做演绎做品 。

相关文章
相关标签/搜索