v19.2Latest

响应事件

React 允许你向 JSX 添加事件处理函数。事件处理函数是你自己的函数,它会在用户交互时被触发,例如点击、悬停、聚焦表单输入等。

您将学习
  • 编写事件处理函数的不同方式
  • 如何从父组件传递事件处理逻辑
  • 事件如何传播以及如何阻止它们

添加事件处理函数

要添加一个事件处理函数,你需要先定义一个函数,然后将其作为 prop 传递给相应的 JSX 标签。例如,下面是一个目前什么都不做的按钮:

你可以通过以下三个步骤使其在用户点击时显示一条消息:

  1. 在你的Button组件内部声明一个名为handleClick的函数。
  2. 在该函数内部实现逻辑(使用alert来显示消息)。
  3. <button>JSX 添加onClick={handleClick}

你定义了handleClick函数,然后将其作为 prop 传递给了<button>handleClick是一个事件处理函数。事件处理函数:

  • 通常定义在组件内部
  • 名称以handle开头,后接事件名称。

按照惯例,通常将事件处理函数命名为handle后接事件名称。你经常会看到onClick={handleClick}onMouseEnter={handleMouseEnter}等等。

或者,你也可以在 JSX 中内联定义事件处理函数:

或者,使用箭头函数更简洁地编写:

这些写法都是等效的。内联事件处理函数对于短函数来说很方便。

常见陷阱

传递给事件处理器的函数必须是传递,而不是调用。例如:

传递函数(正确)调用函数(错误)
<button onClick={handleClick}><button onClick={handleClick()}>

区别很微妙。在第一个例子中,handleClick 函数作为 onClick事件处理器被传递。这告诉 React 记住它,并且只在用户点击按钮时调用你的函数。

在第二个例子中,handleClick() 末尾的 () 会在 立即执行该函数,而无需任何点击。这是因为渲染 过程中 JSX { 和 }中的 JavaScript 会立即执行。

当你编写内联代码时,同样的陷阱会以不同的方式出现:

传递函数(正确)调用函数(错误)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

像这样传递内联代码不会在点击时触发——它会在每次组件渲染时触发:

如果你想内联定义事件处理器,请将其包装在一个匿名函数中,如下所示:

这样不会在每次渲染时执行其中的代码,而是创建一个稍后调用的函数。

在这两种情况下,你想要传递的都是一个函数:

  • <button onClick={handleClick}> 传递了 handleClick函数。
  • <button onClick={() => alert('...')}> 传递了 () => alert('...')函数。

阅读更多关于箭头函数的信息。

在事件处理程序中读取 props

由于事件处理程序是在组件内部声明的,因此它们可以访问组件的 props。这是一个按钮,点击时会显示一个包含其messageprop 的警告框:

这样可以让这两个按钮显示不同的消息。尝试修改传递给它们的消息。

将事件处理函数作为 props 传递

通常你会希望父组件来指定子组件的事件处理函数。以按钮为例:根据你使用 Button组件的位置,你可能希望执行不同的函数——也许一个播放电影,另一个上传图片。

为此,将组件从其父组件接收到的 prop 作为事件处理函数传递,如下所示:

这里,Toolbar组件渲染了一个PlayButton和一个UploadButton

  • PlayButtonhandlePlayClick 作为 onClickprop 传递给内部的Button
  • UploadButton() => alert('Uploading!') 作为 onClickprop 传递给内部的Button

最后,你的 Button 组件接收一个名为 onClick的 prop。它将该 prop 直接传递给内置的浏览器<button>,使用onClick={onClick}。这告诉 React 在点击时调用传递的函数。

如果你使用设计系统,像按钮这样的组件通常包含样式但不指定行为。相反,像 PlayButtonUploadButton这样的组件会将事件处理函数向下传递。

事件处理函数 prop 的命名

<button><div>这样的内置组件只支持浏览器事件名称,例如onClick。然而,当你构建自己的组件时,你可以随意命名它们的事件处理函数 prop。

按照惯例,事件处理函数 prop 应以on开头,后跟一个大写字母。

例如,Button 组件的 onClickprop 本可以命名为onSmash

在这个例子中,<button onClick={onSmash}> 表明浏览器 <button>(小写)仍然需要一个名为onClick 的 prop,但你的自定义 Button组件接收的 prop 名称由你决定!

当你的组件支持多种交互时,你可能会根据应用特定的概念来命名事件处理函数 prop。例如,这个Toolbar 组件接收 onPlayMovieonUploadImage事件处理函数:

请注意,App 组件不需要知道 具体Toolbar将如何处理onPlayMovieonUploadImage。这是 Toolbar的实现细节。在这里,Toolbar 将它们作为 onClick处理函数传递给其Button 组件,但之后也可能通过键盘快捷键来触发它们。根据应用特定的交互(如 onPlayMovie)来命名属性,可以让你在后续灵活地改变它们的使用方式。

注意

请确保为事件处理函数使用合适的 HTML 标签。例如,要处理点击事件,请使用<button onClick={handleClick}>,而不是<div onClick={handleClick}>。使用真正的浏览器<button>可以启用内置的浏览器行为,如键盘导航。如果你不喜欢按钮的默认浏览器样式,并希望它看起来更像链接或其他 UI 元素,可以通过 CSS 来实现。了解更多关于编写无障碍标记的信息。

事件传播

事件处理函数也会捕获组件可能拥有的任何子组件的事件。我们说事件会“冒泡”或“传播”到树的上层:它从事件发生的地方开始,然后沿着树向上传播。

这个<div> 包含两个按钮。<div>每个按钮都有各自的onClick处理函数。当你点击一个按钮时,你认为哪些处理函数会被触发?

如果你点击任意一个按钮,它的onClick 会首先执行,然后是父级 <div>onClick。因此会显示两条消息。如果你点击工具栏本身,则只有父级<div>onClick会执行。

陷阱

在 React 中,除了onScrollonScroll 之外的所有事件都会传播,而 仅作用于你附加它的 JSX 标签。

阻止传播

事件处理函数接收一个事件对象作为唯一的参数。按照惯例,它通常被称为e,代表“event”(事件)。你可以使用这个对象来读取事件的相关信息。

该事件对象也允许你阻止传播。如果你想阻止事件到达父组件,你需要像下面这个 Buttone.stopPropagation()组件一样调用

当你点击一个按钮时:

  1. React 调用传递给onClick的处理函数。
  2. 该处理函数在 Button组件中定义,执行以下操作:
    • 调用e.stopPropagation(),阻止事件进一步冒泡。
    • 调用 onClick 函数,该函数是从 Toolbar组件传递下来的 prop。
  3. 该函数在 Toolbar组件中定义,显示按钮自身的警告框。
  4. 由于传播被阻止,父级 <div>onClick 处理函数不会运行。

由于 e.stopPropagation()的作用,现在点击按钮只会显示一个警告框(来自<button>),而不是两个(分别来自<button> 和父级工具栏的 <div>)。点击按钮与点击其周围的工具栏是不同的操作,因此对于这个 UI 来说,阻止传播是合理的。

Deep Dive
捕获阶段事件

将处理函数作为传播的替代方案传递

请注意,这个点击处理函数如何运行一行代码然后调用父组件传递的onClick 属性:

您也可以在此处理函数中调用父组件 onClick事件处理函数之前添加更多代码。这种模式提供了一种传播的替代方案。它让子组件处理事件,同时也让父组件指定一些额外的行为。与传播不同,它不是自动的。但这种模式的好处是,您可以清晰地跟踪因某个事件而执行的整个代码链。

如果您依赖传播,并且很难追踪哪些处理函数执行以及为什么执行,请尝试改用这种方法。

阻止默认行为

某些浏览器事件具有与之关联的默认行为。例如,<form> 提交事件(当其中的按钮被点击时发生)默认会重新加载整个页面:

你可以在事件对象上调用e.preventDefault() 来阻止这种情况发生:

不要混淆 e.stopPropagation()e.preventDefault()。它们都很有用,但彼此无关:

事件处理函数可以有副作用吗?

当然可以!事件处理函数是产生副作用的最佳场所。

与渲染函数不同,事件处理函数不需要是纯函数,因此它是改变某些东西的理想位置——例如,根据输入内容改变输入框的值,或者根据按钮点击改变列表。然而,为了改变某些信息,你首先需要某种方式来存储它。在 React 中,这是通过使用状态(组件的记忆)来实现的。你将在下一页学到所有相关内容。

回顾

  • 你可以通过将函数作为 prop 传递给像<button>这样的元素来处理事件。
  • 事件处理函数必须被传递,而不是被调用!onClick={handleClick},而不是onClick={handleClick()}
  • 你可以单独定义事件处理函数,也可以内联定义。
  • 事件处理函数在组件内部定义,因此可以访问 props。
  • 你可以在父组件中声明事件处理函数,并将其作为 prop 传递给子组件。
  • 你可以定义具有特定应用名称的自定义事件处理函数 prop。
  • 事件会向上传播。在第一个参数上调用e.stopPropagation()可以阻止传播。
  • 事件可能具有不需要的默认浏览器行为。调用e.preventDefault()可以阻止该行为。
  • 从子组件的事件处理函数中显式调用父组件传递的事件处理函数 prop,是替代事件传播的一种好方法。

尝试一些挑战

Challenge 1 of 2:修复事件处理器 #

点击此按钮应切换页面背景色为白色和黑色。然而,点击时没有任何反应。请修复此问题。(不必担心 handleClick 内部的逻辑——那部分没问题。)