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:
-
在
Button
组件中声明一个名为handleClick
的函数。¥Declare a function called
handleClick
inside yourButton
component. -
在该函数内实现逻辑(使用
alert
显示消息)。¥Implement the logic inside that function (use
alert
to show the message). -
将
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.
在事件处理程序中读取属性
¥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
组件渲染 PlayButton
和 UploadButton
:
¥Here, the Toolbar
component renders a PlayButton
and an UploadButton
:
-
PlayButton
将handlePlayClick
作为onClick
属性传给里面的Button
。¥
PlayButton
passeshandlePlayClick
as theonClick
prop to theButton
inside. -
UploadButton
将() => alert('Uploading!')
作为onClick
属性传给里面的Button
。¥
UploadButton
passes() => alert('Uploading!')
as theonClick
prop to theButton
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.
如果你使用 设计系统,像按钮等组件通常包含样式但不指定行为。而是,像 PlayButton
和 UploadButton
这样的组件将向下传递事件处理程序。
¥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
组件接收 onPlayMovie
和 onUploadImage
事件处理程序:
¥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
将对 onPlayMovie
或 onUploadImage
做什么。这是 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 Button
s, 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.
事件传播
¥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.
停止传播
¥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:
-
React 调用传递给
<button>
的onClick
处理程序。¥React calls the
onClick
handler passed to<button>
. -
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 theToolbar
component.
-
-
该函数在
Toolbar
组件中定义,显示按钮自己的警报。¥That function, defined in the
Toolbar
component, displays the button’s own alert. -
由于传播已停止,父
<div>
的onClick
处理程序不会运行。¥Since the propagation was stopped, the parent
<div>
’sonClick
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:
-
它向下移动,调用所有
onClickCapture
处理程序。¥It travels down, calling all
onClickCapture
handlers. -
它运行被单击元素的
onClick
处理程序。¥It runs the clicked element’s
onClick
handler. -
它向上移动,调用所有
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:
-
e.stopPropagation()
停止触发附加到上述标签的事件处理程序。¥
e.stopPropagation()
stops the event handlers attached to the tags above from firing. -
e.preventDefault()
阻止了少数事件的默认浏览器行为。¥
e.preventDefault()
prevents the default browser behavior for the few events that have it.
事件处理程序可以有副作用吗?
¥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}
, notonClick={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> ); }