资讯专栏INFORMATION COLUMN

玩转 React(六)- 处理事件

Astrian / 1277人阅读

摘要:绑定事件处理函数指向的四中方式以及他们的优缺点。内部自己实现了一套高效的事件机制,为了提高框架的性能,通过事件冒泡,只在节点上注册原生的事件,内部自己管理所有组件的事件处理函数,以及事件的冒泡捕获。

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

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

好的,言归正传,今天我们说一下在 React 中是如何处理事件的。事件处理是前端开发过程中非常重要的一部分,通过事件处理机制,我们的前端应用可以响应用户的各种操作,从而实现一个富交互的前端应用。

内容摘要

如何为 React 的内置组件设置事件处理函数。

React 事件对象与浏览器原生 DOM 事件对象的区别。

默认情况下不能以异步的方式使用事件对象,如在 setTimeout 中。

不要在组件中使用 addEventListener 注册事件处理函数,有坑。

绑定事件处理函数 this 指向的四中方式以及他们的优缺点。

React 内置组件的事件处理

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

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

第一种方式


    Click me.

这是一种古老的方式,在 DOM level 1 规范中的事件注册方式,现在已经很少使用了。

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

可以通过 return false; 来阻止当前 HMTL 元素的默认行为,如 a 标签的页面跳转。

关于 DOM 规范的级别可以参考:DOM Levels

第二种方式:


    Click me.


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

在 React 中,事件注册与方式一非常类似,不过有如下几点不同:

属性名称采用驼峰式(如:onClick,onKeyDown),而不是全小写字母。

属性值接受一个函数,而不是字符串。

return false; 不会阻止组件的默认行为,需要调用 e.preventDefault();

如下所示:

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

  return (
    
      Click me
    
  );
}

这是一个以函数方式定义的组件,组件渲染一个 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({ count: this.state.count + 1 });
  }
  render() {
    return (
      
    )
  }
}
ReactDOM.render(, document.querySelector("#root"));

https://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({ count: this.state.count + 1 })
  }
  render() {
    return Clicked me {this.state.count} times.    
  }

}

ReactDOM.render(, document.querySelector("#root"))

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

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

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


    Clicked me {this.state.count} times.

这种方式的优点是书写起来相对简单,但是每次渲染都会执行 bind 方法生成一个新的函数,会有额外的开销,由于事件处理函数是作为属性传递的,所以从而导致子组件进行重新渲染,显然这不是一种好的方式。

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

 this.handleClick(e)}>
    Clicked me {this.state.count} times.

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

第三种方式,在 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({ count: this.state.count + 1 })
  }
  render() {
    return Clicked me {this.state.count} times.    
  }
}

ReactDOM.render(, document.querySelector("#root"))

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

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

class Link extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  handleClick = e => {
    e.preventDefault();
    this.setState({ count: this.state.count + 1 })
  }
  render() {
    return Clicked me {this.state.count} times.    
  }
}
ReactDOM.render(, document.querySelector("#root"))

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

不过这也没太大关系。

总结

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

好了,有什么疑问可以加微信群交流,我的微信号:leobaba88,验证信息:玩转 React。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89549.html

相关文章

  • 玩转 React(一)- 前言

    摘要:本人计划编写一个针对中初级前端开发者学习的系列教程玩转。使用的原因是新的语言规范开发效率更高代码更优雅,尤其是基于开发的项目。其次也是目前特别流行的一个前端框架,截止目前,上有将近万,国内一二线互联网公司都有深度依赖开发的项目。 本人计划编写一个针对中初级前端开发者学习 React 的系列教程 - 《玩转 React》。 文章更新频率:每周 1 ~ 2 篇。 目录 玩转 React(...

    waltr 评论0 收藏0
  • 玩转 React(七)- 组件之间的数据共享

    摘要:函数属性或者说事件在组件之间通信过程中是必不可少的,但是切莫让它影响了大家对单向数据流这一概念的理解。这应该属于一种的使用方式,而且这样做有悖单向数据流原则。 上一篇文章 玩转 React(六)- 处理事件 介绍了在 React 中如何处理用户事件,以及 React 事件机制与原生 DOM 事件的差异和注意的问题,同时也介绍了事件处理函数中 this 的指向问题以及处理的几种方式及其优...

    Gu_Yan 评论0 收藏0
  • 玩转 React(五)- 组件的内部状态和生命周期

    摘要:另外本文中会介绍一个通过类继承方式定义的组件的生命周期,以及在各个生命周期函数中能做什么,不能或尽量不要做什么。各个生命周期函数介绍及使用经验。获取组件的初始内部状态在中。该声明周期函数可能在两种情况下被调用组件接收到了新的属性。 文章标题总算是可以正常一点了…… 通过之前的文章我们已经知道:在 React 体系中所谓的 在 JavaScript 中编写 HTML 代码 指的是 Rea...

    Rocture 评论0 收藏0
  • 玩转 React(五)- 组件的内部状态和生命周期

    摘要:另外本文中会介绍一个通过类继承方式定义的组件的生命周期,以及在各个生命周期函数中能做什么,不能或尽量不要做什么。各个生命周期函数介绍及使用经验。获取组件的初始内部状态在中。该声明周期函数可能在两种情况下被调用组件接收到了新的属性。 文章标题总算是可以正常一点了…… 通过之前的文章我们已经知道:在 React 体系中所谓的 在 JavaScript 中编写 HTML 代码 指的是 Rea...

    ASCH 评论0 收藏0
  • React源码剖析系列 - 玩转 React Transition

    摘要:后面将会仔细分析的源码实现。更新完成后,对中的每个元素执行动画的逻辑,对中的每个元素执行动画的逻辑。事实上,原因很简单,事件在某些情况是不会被触发。总结动画是组件初次后,才会被添加到的所有子元素上。参考资料官方文档事件 过去一年,React 给整个前端界带来了一种新的开发方式,我们抛弃了无所不能的 DOM 操作。对于 React 实现动画这个命题,DOM 操作已经是一条死路,而 CSS...

    rottengeek 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<