如何在React中处理事件

117 阅读6分钟

React元素的属性是基于标准DOM事件的。React元素能够触发所有与普通DOM元素相同的事件。它们是用camelCase命名的,所以不要忘记在React元素上使用这个惯例。因此,如果你想使用onclick,它实际上将是onClick。这与不使用class,而使用className的情况很相似。React处理了不同类型的事件,但我们在这里只说基本的。


添加一个onClick属性

例如,我们可以将 onClick属性来处理一个事件。它被设置为大括号{ },因为在React中,这就是我们传递表达式的方式。

import React, { Component } from "react";

class Item extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span className={this.styleBadge()}>{this.styleCount()}</span>
        <button onClick={} className="btn btn-primary">Increment</button>
      </React.Fragment>
    );
  }

  styleBadge() {
    let classes = "badge m-3 badge-";
    classes += this.state.count === 0 ? "warning" : "info";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items" : count;
  }
}

export default Item;

现在我们可以做的是在渲染方法之上但在状态对象之下创建一个可以处理事件的方法。惯例是,这些类型的方法总是以handle开头。所以我们的方法将是handleIncrement()。在这一点上,我们想要的是一些证据,证明按钮被点击了。所以要开始了,在这里。

import React, { Component } from "react";

class Item extends Component {
  state = {
    count: 0
  };

  handleIncrement() {
    alert("clicked!");
  }

  render() {
    return (
      <React.Fragment>
        <span className={this.styleBadge()}>{this.styleCount()}</span>
        <button onClick={this.handleIncrement} className="btn btn-primary">
          Increment
        </button>
      </React.Fragment>
    );
  }

  styleBadge() {
    let classes = "badge m-3 badge-";
    classes += this.state.count === 0 ? "warning" : "info";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items" : count;
  }
}

export default Item;

还要注意的是,在按钮元素上,我们现在使用this.handleIncrement传递一个对该函数的引用。这个方法并没有被调用,而只是传递了一个引用,这与你在普通JavaScript中看到的不同。好的,让我们看看它的运行情况。

我知道,令人印象深刻!


更新状态

好的,下一个目标是当按钮被点击时,改变状态对象中的计数属性的值。但首先,我们需要看到在JavaScript中,有时是令人困惑的。我们想访问这个计数值,所以我们可以尝试这样做。

  handleIncrement() {
    alert(this.state.count);
  }

现在试着点击这个按钮,你会看到一个错误。

未发现的类型错误。不能读取未定义的属性'state'。

这种情况很可能不止一次发生在你身上!当你搞不清楚为什么会发生这种情况时,也会感到很沮丧。


在React中绑定事件处理程序

为了解决这个问题,我们需要看一下如何在React中绑定事件。在JavaScript中,事件的上下文是 this的上下文会根据函数或方法的调用方式而改变。有一些规则需要注意,但简短的答案是,有时我们需要使用bind()方法显式地绑定这个。让我们看看如何做到这一点。


构造函数和超级

这是绕过这个讨厌的Uncaught TypeError的第一个方法*。不能读取未定义的属性'state'的*错误的第一个方法是给类添加一个构造函数,同时调用super(),然后像这样使用bind()方法。

import React, { Component } from "react";

class Item extends Component {
  constructor() {
    super();
    this.handleIncrement = this.handleIncrement.bind(this);
  }

  state = {
    count: 0
  };

  handleIncrement() {
    alert(this.state.count);
  }

  render() {
    return (
      <React.Fragment>
        <span className={this.styleBadge()}>{this.styleCount()}</span>
        <button onClick={this.handleIncrement} className="btn btn-primary">
          Increment
        </button>
      </React.Fragment>
    );
  }

  styleBadge() {
    let classes = "badge m-3 badge-";
    classes += this.state.count === 0 ? "warning" : "info";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items" : count;
  }
}

export default Item;

啊哈!现在我们可以访问state对象的count属性中的值了。


箭头函数作为替代

上面使用构造函数、super()和bind()的解决方案可能会很繁琐。每一个事件处理程序都需要这样做,所以最后会有很多重复的代码。另一个解决这个绑定问题的方法是使用箭头函数来代替。我们把计数也更新为5,所以当我们测试时可以看到一个新的结果。下面是方法。

import React, { Component } from "react";

class Item extends Component {
  state = {
    count: 5
  };

  handleIncrement = () => {
    alert(this.state.count);
  };

  render() {
    return (
      <React.Fragment>
        <span className={this.styleBadge()}>{this.styleCount()}</span>
        <button onClick={this.handleIncrement} className="btn btn-primary">
          Increment
        </button>
      </React.Fragment>
    );
  }

  styleBadge() {
    let classes = "badge m-3 badge-";
    classes += this.state.count === 0 ? "warning" : "info";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items" : count;
  }
}

export default Item;

这是一个不错的解决方案,因为现在,你不需要总是在构造函数中为每个事件处理程序添加一个新的bind()。你只需将一个箭头函数分配给类上的一个属性,就可以 this得到正确的绑定。


更新状态

现在我们的工作很顺利,我们可以着手更新状态。在使用React时要记住的一个关键点是不要直接修改日期。换句话说,不要尝试做这样的事情。

// No!
  handleIncrement = () => {
    this.state.count = this.state.count + 1;
  };

setState来拯救

要在反应中更新状态,你可以使用setState()方法。这告诉React有一个状态的更新,然后React找出它需要更新的DOM的哪个部分--并这样做。其他一些流行的MVVM框架会自动完成这一步骤,但在React中,我们确实需要像这样使用setState()方法。

  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };

这很有效!

setState()方法明确地告诉React,这个组件的状态要改变。现在,当render()方法最终被触发时,它会返回一个新的React元素,其中包含任何已经进行过的更新。在这一点上,React比较了新与旧的虚拟DOM,并找出了不同之处。然后,它只用那些需要的变化来更新Real DOM,而不是整个DOM树。这就是使性能非常快的原因。事实上,你可以在浏览器中看到这一点,如果你打开开发者工具,看看状态被更新时有什么变化。非常酷


如何传递事件参数

在本教程中,我们可以看的最后一件事是如何传递一个事件的参数。我们想用一个参数来触发onClick。这有点棘手,所以我们需要一个图表。
react pass event arguments
为了给事件处理程序传递一个参数,我们可以使用一个内联箭头函数。因此,与其使用这个

onClick={this.handleIncrement}

我们现在将使用这个

onClick={() => {this.handleIncrement({ message: "Its Friday!" });}}

在上图中,这由橙色的圆圈表示。现在注意到,内联箭头函数被传递给一个对象作为参数。这个参数现在对handleIncrement事件处理程序可用。事实上,在该函数的主体中,我们可以提醒出e.message,事实上我们看到屏幕上出现了 "It Friday!"的值。
react pass event argument example


总结

在处理React元素的事件时,我们可以采取与处理普通DOM元素的事件类似的方法。有一些区别,比如React事件是用camelCase命名的,而且你传递一个函数作为事件处理器,而不是一个字符串。当使用类来定义一个组件时,一个常见的模式是将事件处理程序作为类的一个方法,就像我们在本教程中所做的那样。