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.

你将学习到

  • 编写事件处理程序的不同方法

    ¥Different ways to write an event handler

  • 如何从父组件传递事件处理逻辑

    ¥How to pass event handling logic from a parent component

  • 事件如何传播以及如何阻止它们

    ¥How events propagate and how to stop them

添加事件处理程序

¥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 的函数。

    ¥Declare a function called handleClick inside your Button component.

  2. 在该函数内实现逻辑(使用 alert 显示消息)。

    ¥Implement the logic inside that function (use alert to show the message).

  3. onClick={handleClick} 添加到 <button> JSX。

    ¥Add onClick={handleClick} to the <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:

  • 通常在你的组件内部定义。

    ¥Are usually defined inside your components.

  • 名称以 handle 开头,后跟事件名称。

    ¥Have names that start with handle, followed by the name of the event.

按照惯例,通常将事件处理程序命名为 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={handleClick}> passes the handleClick function.

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

    ¥<button onClick={() => alert('...')}> passes the () => alert('...') function.

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

¥Read more about arrow functions.

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

¥Reading props in event handlers

因为事件处理程序是在组件内部声明的,所以它们可以访问组件的属性。这是一个按钮,单击该按钮时,会显示带有 message 属性的警报:

¥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 组件渲染 PlayButtonUploadButton

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

  • PlayButtonhandlePlayClick 作为 onClick 属性传给里面的 Button

    ¥PlayButton passes handlePlayClick as the onClick prop to the Button inside.

  • UploadButton() => alert('Uploading!') 作为 onClick 属性传给里面的 Button

    ¥UploadButton passes () => alert('Uploading!') as the onClick prop to the Button inside.

最后,你的 Button 组件接受一个名为 onClick 的属性。它将该属性直接传递给带有 onClick={onClick} 的内置浏览器 <button>。这告诉 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,但它稍后也可以通过键盘快捷键触发它们。在像 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.

易犯错误

除了 onScroll,所有事件都在 React 中传播,它仅适用于你附加的 JSX 标签。

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

停止传播

¥Stopping propagation

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

¥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 处理程序。

    ¥React calls the onClick handler passed to <button>.

  2. Button 中定义的该处理程序执行以下操作:

    ¥That handler, defined in Button, does the following:

    • 调用 e.stopPropagation(),防止事件进一步冒泡。

      ¥Calls e.stopPropagation(), preventing the event from bubbling further.

    • 调用 onClick 函数,它是从 Toolbar 组件传递的属性。

      ¥Calls the onClick function, which is a prop passed from the Toolbar component.

  3. 该函数在 Toolbar 组件中定义,显示按钮自己的警报。

    ¥That function, defined in the Toolbar component, displays the button’s own alert.

  4. 由于传播已停止,父 <div>onClick 处理程序不会运行。

    ¥Since the propagation was stopped, the parent <div>’s onClick handler does not run.

作为 e.stopPropagation() 的结果,单击按钮现在仅显示一个警报(来自 <button>)而不是其中的两个(来自 <button> 和父工具栏 <div>)。单击按钮与单击周围的工具栏不同,因此停止传播对于此 UI 来说是有意义的。

¥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 处理程序。

    ¥It travels down, calling all onClickCapture handlers.

  2. 它运行被单击元素的 onClick 处理程序。

    ¥It runs the clicked element’s onClick handler.

  3. 它向上移动,调用所有 onClick 处理程序。

    ¥It travels upwards, calling all onClick handlers.

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

¥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> 这样的元素来处理事件。

    ¥You can handle events by passing a function as a prop to an element like <button>.

  • 事件处理程序必须被传递,而不是被调用!onClick={handleClick},不是 onClick={handleClick()}

    ¥Event handlers must be passed, not called! onClick={handleClick}, not onClick={handleClick()}.

  • 你可以单独或内联定义事件处理函数。

    ¥You can define an event handler function separately or inline.

  • 事件处理程序在组件内部定义,因此它们可以访问属性。

    ¥Event handlers are defined inside a component, so they can access props.

  • 你可以在父级中声明一个事件处理程序并将其作为属性传递给子级。

    ¥You can declare an event handler in a parent and pass it as a prop to a child.

  • 你可以使用特定于应用的名称定义自己的事件处理程序属性。

    ¥You can define your own event handler props with application-specific names.

  • 事件向上传播。在第一个参数上调用 e.stopPropagation() 以防止出现这种情况。

    ¥Events propagate upwards. Call e.stopPropagation() on the first argument to prevent that.

  • 事件可能有不需要的默认浏览器行为。致电 e.preventDefault() 以防止出现这种情况。

    ¥Events may have unwanted default browser behavior. Call e.preventDefault() to prevent that.

  • 从子处理程序显式调用事件处理程序属性是传播的一个很好的替代方法。

    ¥Explicitly calling an event handler prop from a child handler is a good alternative to propagation.

挑战 1 / 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>
  );
}


React 中文网 - 粤ICP备13048890号