React 让你可以向你的 JSX 添加事件处理器。事件处理器是你自己的函数,它们会在点击、悬停、聚焦表单输入等交互发生时被触发。

🌐 React lets you add event handlers to your JSX. Event handlers are your own functions that will be triggered in response to interactions like clicking, hovering, focusing form inputs, and so on.

你将学习到

  • 编写事件处理程序的不同方法
  • 如何从父组件传递事件处理逻辑
  • 事件如何传播以及如何阻止它们

添加事件处理程序

🌐 Adding event handlers

要添加一个事件处理程序,你首先需要定义一个函数,然后将其作为属性传递给相应的 JSX 标签。例如,这里有一个尚未执行任何操作的按钮:

🌐 To add an event handler, you will first define a function and then pass it as a prop to the appropriate JSX tag. For example, here is a button that doesn’t do anything yet:

export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

你可以按照以下三个步骤使其在用户单击时显示消息:

🌐 You can make it show a message when a user clicks by following these three steps:

  1. 在你的 Button 组件中声明一个名为 handleClick 的函数。
  2. 在该函数内部实现逻辑(使用 alert 显示消息)。
  3. onClick={handleClick} 添加到 <button> JSX 中。
export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

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

🌐 You defined the handleClick function and then passed it as a prop to <button>. handleClick is an event handler. Event handler functions:

  • 通常在你的组件内部定义。
  • 名称以 handle 开头,后面跟活动名称。

按照惯例,通常将事件处理程序命名为 handle 加上事件名称。你经常会看到 onClick={handleClick}onMouseEnter={handleMouseEnter} 等等。

🌐 By convention, it is common to name event handlers as handle followed by the event name. You’ll often see onClick={handleClick}, onMouseEnter={handleMouseEnter}, and so on.

或者,你可以在 JSX 中内联定义事件处理程序:

🌐 Alternatively, you can define an event handler inline in the JSX:

<button onClick={function handleClick() {
alert('You clicked me!');
}}>

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

🌐 Or, more concisely, using an arrow function:

<button onClick={() => {
alert('You clicked me!');
}}>

所有这些样式都是等效的。内联事件处理程序对于短函数来说非常方便。

🌐 All of these styles are equivalent. Inline event handlers are convenient for short functions.

易犯错误

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

🌐 Functions passed to event handlers must be passed, not called. For example:

传递一个函数(正确)调用一个函数(不正确)
<button onClick={handleClick}><button onClick={handleClick()}>

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

🌐 The difference is subtle. In the first example, the handleClick function is passed as an onClick event handler. This tells React to remember it and only call your function when the user clicks the button.

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

🌐 In the second example, the () at the end of handleClick() fires the function immediately during rendering, without any clicks. This is because JavaScript inside the JSX { and } executes right away.

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

🌐 When you write code inline, the same pitfall presents itself in a different way:

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

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

🌐 Passing inline code like this won’t fire on click—it fires every time the component renders:

// This alert fires when the component renders, not when clicked!
<button onClick={alert('You clicked me!')}>

如果你想内联定义事件处理程序,请将其封装在匿名函数中,如下所示:

🌐 If you want to define your event handler inline, wrap it in an anonymous function like so:

<button onClick={() => alert('You clicked me!')}>

这不是在每次渲染时都执行内部代码,而是创建一个稍后调用的函数。

🌐 Rather than executing the code inside with every render, this creates a function to be called later.

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

🌐 In both cases, what you want to pass is a function:

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

阅读有关箭头函数的更多内容。

在事件处理程序中读取属性

🌐 Reading props in event handlers

因为事件处理程序是在组件内部声明的,所以它们可以访问组件的 props。下面是一个按钮,当点击时,会显示包含其 message prop 的提醒框:

🌐 Because event handlers are declared inside of a component, they have access to the component’s props. Here is a button that, when clicked, shows an alert with its message prop:

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

这让这两个按钮显示不同的消息。试着更改传递给它们的消息。

🌐 This lets these two buttons show different messages. Try changing the messages passed to them.

将事件处理程序作为属性传递

🌐 Passing event handlers as props

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

🌐 Often you’ll want the parent component to specify a child’s event handler. Consider buttons: depending on where you’re using a Button component, you might want to execute a different function—perhaps one plays a movie and another uploads an image.

为此,传递组件从其父级接收的属性作为事件处理程序,如下所示:

🌐 To do this, pass a prop the component receives from its parent as the event handler like so:

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}

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

🌐 Here, the Toolbar component renders a PlayButton and an UploadButton:

  • PlayButtonhandlePlayClick 作为 Button 内部的 onClick 属性传递。
  • UploadButton() => alert('Uploading!') 作为 Button 内部的 onClick 属性传递。

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

🌐 Finally, your Button component accepts a prop called onClick. It passes that prop directly to the built-in browser <button> with onClick={onClick}. This tells React to call the passed function on click.

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

🌐 If you use a design system, it’s common for components like buttons to contain styling but not specify behavior. Instead, components like PlayButton and UploadButton will pass event handlers down.

命名事件处理程序属性

🌐 Naming event handler props

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

🌐 Built-in components like <button> and <div> only support browser event names like onClick. However, when you’re building your own components, you can name their event handler props any way that you like.

按照惯例,事件处理程序属性应以 on 开头,后跟一个大写字母。

🌐 By convention, event handler props should start with on, followed by a capital letter.

例如,Button 组件的 onClick 属性本可以叫做 onSmash

🌐 For example, the Button component’s onClick prop could have been called onSmash:

function Button({ onSmash, children }) {
  return (
    <button onClick={onSmash}>
      {children}
    </button>
  );
}

export default function App() {
  return (
    <div>
      <Button onSmash={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onSmash={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

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

🌐 In this example, <button onClick={onSmash}> shows that the browser <button> (lowercase) still needs a prop called onClick, but the prop name received by your custom Button component is up to you!

当你的组件支持多种交互时,你可能会为应用特定的概念命名事件处理器属性。例如,这个 Toolbar 组件接收 onPlayMovieonUploadImage 事件处理器:

🌐 When your component supports multiple interactions, you might name event handler props for app-specific concepts. For example, this Toolbar component receives onPlayMovie and onUploadImage event handlers:

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

注意 App 组件不需要知道 Toolbar 会如何处理 onPlayMovieonUploadImage。那是 Toolbar 的实现细节。在这里,Toolbar 将它们作为 onClick 处理函数传递给它的 Button,但它以后也可以在键盘快捷键上触发它们。将 props 命名为像 onPlayMovie 这样的应用特定交互,可以让你以后灵活地改变它们的使用方式。

🌐 Notice how the App component does not need to know what Toolbar will do with onPlayMovie or onUploadImage. That’s an implementation detail of the Toolbar. Here, Toolbar passes them down as onClick handlers to its Buttons, but it could later also trigger them on a keyboard shortcut. Naming props after app-specific interactions like onPlayMovie gives you the flexibility to change how they’re used later.

注意

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

🌐 Make sure that you use the appropriate HTML tags for your event handlers. For example, to handle clicks, use <button onClick={handleClick}> instead of <div onClick={handleClick}>. Using a real browser <button> enables built-in browser behaviors like keyboard navigation. If you don’t like the default browser styling of a button and want to make it look more like a link or a different UI element, you can achieve it with CSS. Learn more about writing accessible markup.

事件传播

🌐 Event propagation

事件处理程序还会捕获组件可能拥有的任何子元素的事件。我们说一个事件会“冒泡”或“在树上传播”:它从事件发生的地方开始,然后向上传播到树的其他部分。

🌐 Event handlers will also catch events from any children your component might have. We say that an event “bubbles” or “propagates” up the tree: it starts with where the event happened, and then goes up the tree.

这个 <div> 包含两个按钮。<div> 和每个按钮都有它们自己的 onClick 处理程序。当你点击一个按钮时,你认为哪些处理程序会被触发?

🌐 This <div> contains two buttons. Both the <div> and each button have their own onClick handlers. Which handlers do you think will fire when you click a button?

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}

如果你点击任意一个按钮,它的 onClick 会先运行,然后是父级 <div>onClick。所以会出现两个消息。如果你点击工具栏本身,只有父级 <div>onClick 会运行。

🌐 If you click on either button, its onClick will run first, followed by the parent <div>’s onClick. So two messages will appear. If you click the toolbar itself, only the parent <div>’s onClick will run.

易犯错误

在 React 中所有事件都会传播,除了 onScroll,它只在你附加它的 JSX 标签上起作用。

🌐 All events propagate in React except onScroll, which only works on the JSX tag you attach it to.

停止传播

🌐 Stopping propagation

事件处理程序将 事件对象 作为它们的唯一参数。按惯例,它通常被称为 e,代表“事件”。你可以使用此对象来读取有关事件的信息。

🌐 Event handlers receive an event object as their only argument. By convention, it’s usually called e, which stands for “event”. You can use this object to read information about the event.

该事件对象还允许你停止传播。如果你想阻止事件到达父组件,你需要像 Button 组件这样调用 e.stopPropagation()

🌐 That event object also lets you stop the propagation. If you want to prevent an event from reaching parent components, you need to call e.stopPropagation() like this Button component does:

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <Button onClick={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onClick={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

当你点击一个按钮时:

🌐 When you click on a button:

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

由于 e.stopPropagation(),现在点击按钮只会显示一个警告(来自 <button>),而不是两个(来自 <button> 和父工具栏 <div>)。点击按钮与点击周围的工具栏不是一回事,因此停止事件传播对这个用户界面来说是合理的。

🌐 As a result of e.stopPropagation(), clicking on the buttons now only shows a single alert (from the <button>) rather than the two of them (from the <button> and the parent toolbar <div>). Clicking a button is not the same thing as clicking the surrounding toolbar, so stopping the propagation makes sense for this UI.

深入研究

捕获阶段事件

🌐 Capture phase events

在罕见的情况下,你可能需要捕获子元素上的所有事件,即使它们已停止冒泡。例如,你可能希望记录每一次点击到分析系统,而不管冒泡逻辑如何。你可以通过在事件名称末尾添加 Capture 来实现这一点:

🌐 In rare cases, you might need to catch all events on child elements, even if they stopped propagation. For example, maybe you want to log every click to analytics, regardless of the propagation logic. You can do this by adding Capture at the end of the event name:

<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>

每个事件都分三个阶段传播:

🌐 Each event propagates in three phases:

  1. 它向下传递,调用所有 onClickCapture 处理程序。
  2. 它运行被点击元素的 onClick 处理程序。
  3. 它向上移动,调用所有 onClick 处理程序。

捕获事件对于路由或分析等代码很有用,但你可能不会在应用代码中使用它们。

🌐 Capture events are useful for code like routers or analytics, but you probably won’t use them in app code.

传递处理程序作为传播的替代方法

🌐 Passing handlers as alternative to propagation

注意这个点击处理程序如何先执行一行代码 然后 调用父组件传递的 onClick 属性:

🌐 Notice how this click handler runs a line of code and then calls the onClick prop passed by the parent:

function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}

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

🌐 You could add more code to this handler before calling the parent onClick event handler, too. This pattern provides an alternative to propagation. It lets the child component handle the event, while also letting the parent component specify some additional behavior. Unlike propagation, it’s not automatic. But the benefit of this pattern is that you can clearly follow the whole chain of code that executes as a result of some event.

如果你依赖于传播并且很难跟踪执行哪些处理程序以及执行原因,请尝试使用此方法。

🌐 If you rely on propagation and it’s difficult to trace which handlers execute and why, try this approach instead.

阻止默认行为

🌐 Preventing default behavior

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

🌐 Some browser events have default behavior associated with them. For example, a <form> submit event, which happens when a button inside of it is clicked, will reload the whole page by default:

export default function Signup() {
  return (
    <form onSubmit={() => alert('Submitting!')}>
      <input />
      <button>Send</button>
    </form>
  );
}

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

🌐 You can call e.preventDefault() on the event object to stop this from happening:

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('Submitting!');
    }}>
      <input />
      <button>Send</button>
    </form>
  );
}

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

🌐 Don’t confuse e.stopPropagation() and e.preventDefault(). They are both useful, but are unrelated:

事件处理程序可以有副作用吗?

🌐 Can event handlers have side effects?

绝对如此!事件处理程序是副作用最好的地方。

🌐 Absolutely! Event handlers are the best place for side effects.

与渲染函数不同,事件处理程序不需要是纯函数,因此它是修改某些内容的好地方——例如,根据输入内容更改输入值,或根据按钮按下更改列表。然而,为了修改某些信息,你首先需要某种存储方式。在 React 中,这是通过使用状态,一个组件的记忆。来实现的。你将在下一页学习所有相关内容。

🌐 Unlike rendering functions, event handlers don’t need to be pure, so it’s a great place to change something—for example, change an input’s value in response to typing, or change a list in response to a button press. However, in order to change some information, you first need some way to store it. In React, this is done by using state, a component’s memory. You will learn all about it on the next page.

回顾

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

挑战 1 of 2:
修复事件处理程序

🌐 Fix an event handler

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

🌐 Clicking this button is supposed to switch the page background between white and black. However, nothing happens when you click it. Fix the problem. (Don’t worry about the logic inside handleClick—that part is fine.)

export default function LightSwitch() {
  function handleClick() {
    let bodyStyle = document.body.style;
    if (bodyStyle.backgroundColor === 'black') {
      bodyStyle.backgroundColor = 'white';
    } else {
      bodyStyle.backgroundColor = 'black';
    }
  }

  return (
    <button onClick={handleClick()}>
      Toggle the lights
    </button>
  );
}