React 允许你向你的 JSX 添加事件处理程序。事件处理程序是你自己的函数,它们将响应点击、悬停、聚焦表单输入等交互而触发。
你将学习
- 编写事件处理程序的不同方法
- 如何从父组件传递事件处理逻辑
- 事件如何传播以及如何阻止它们
添加事件处理程序
要添加事件处理程序,你首先要定义一个函数,然后将其作为 prop 传递到相应的 JSX 标签。例如,这是一个目前什么也不做的按钮
export default function Button() { return ( <button> I don't do anything </button> ); }
你可以通过以下三个步骤,让它在用户点击时显示一条消息
- 在你的
Button
组件内部声明一个名为handleClick
的函数。 - 在该函数内部实现逻辑(使用
alert
显示消息)。 - 将
onClick={handleClick}
添加到<button>
JSX 中。
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
你定义了handleClick
函数,然后将其作为 prop 传递给<button>
。handleClick
是一个事件处理程序。事件处理程序函数
- 通常在你的组件内部定义。
- 名称以
handle
开头,后跟事件名称。
按照惯例,通常将事件处理程序命名为handle
后跟事件名称。你经常会看到onClick={handleClick}
、onMouseEnter={handleMouseEnter}
等等。
或者,你可以在 JSX 中内联定义事件处理程序
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
或者,更简洁地,使用箭头函数
<button onClick={() => {
alert('You clicked me!');
}}>
所有这些样式都是等效的。内联事件处理程序对于简短函数来说很方便。
在事件处理程序中读取 props
因为事件处理程序是在组件内部声明的,所以它们可以访问组件的 props。这是一个按钮,当点击时,它会显示一个带有其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> ); }
这使得这两个按钮可以显示不同的消息。尝试更改传递给它们的 message。
将事件处理程序作为 props 传递
通常,您希望父组件指定子组件的事件处理程序。考虑按钮:根据您使用Button
组件的位置,您可能希望执行不同的函数——也许一个是播放电影,另一个是上传图片。
为此,请将组件从其父组件接收到的 prop 作为事件处理程序传递,如下所示
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
PlayButton
将handlePlayClick
作为onClick
prop 传递给内部的Button
。UploadButton
将() => alert('Uploading!')
作为onClick
prop 传递给内部的Button
。
最后,您的Button
组件接受一个名为onClick
的 prop。它将该 prop 直接传递给内置的浏览器<button>
,并使用onClick={onClick}
。这告诉 React 在点击时调用传递的函数。
如果您使用设计系统,按钮之类的组件通常包含样式,但不指定行为。相反,像PlayButton
和UploadButton
这样的组件将事件处理程序向下传递。
命名事件处理程序 props
像<button>
和<div>
这样的内置组件仅支持浏览器事件名称,例如onClick
。但是,当您构建自己的组件时,您可以随意命名其事件处理程序 props。
按照约定,事件处理程序 props 应以on
开头,后跟一个大写字母。
例如,Button
组件的onClick
prop 可以称为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
的 prop,但是您的自定义Button
组件接收的 prop 名称由您决定!
当您的组件支持多种交互时,您可以为特定于应用程序的概念命名事件处理程序 props。例如,此Toolbar
组件接收onPlayMovie
和onUploadImage
事件处理程序
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
)命名 props 使您可以灵活地更改以后如何使用它们。
事件传播
事件处理程序还将捕获组件可能具有的任何子组件的事件。我们说事件“冒泡”或“传播”到树上:它从事件发生的地方开始,然后向上到树。
这个 <div>
包含两个按钮。这个 <div>
以及每个按钮都各自拥有 onClick
处理程序。你认为点击按钮时,哪些处理程序会触发呢?
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
处理程序会运行。
阻止事件传播
事件处理程序仅接收一个 事件对象 作为其参数。按照惯例,它通常被称为 e
,代表“事件”。您可以使用此对象读取有关事件的信息。
该事件对象还可以让你停止事件传播。如果要阻止事件到达父组件,需要调用 e.stopPropagation()
,就像 Button
组件那样。
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> ); }
当你点击一个按钮时
- React 会调用传递给
<button>
的onClick
处理程序。 - 该处理程序在
Button
中定义,执行以下操作:- 调用
e.stopPropagation()
,阻止事件进一步冒泡。 - 调用
onClick
函数,这是一个从Toolbar
组件传递的 prop。
- 调用
- 该函数在
Toolbar
组件中定义,显示按钮自己的警报。 - 由于事件传播被阻止,父级
<div>
的onClick
处理程序不会运行。
由于 e.stopPropagation()
,点击按钮现在只会显示单个警报(来自 <button>
),而不是两个警报(来自 <button>
和父工具栏 <div>
)。点击按钮与点击周围的工具栏不同,因此对于此 UI 来说,停止事件传播是有意义的。
深入探讨
在极少数情况下,你可能需要捕获子元素上的所有事件,_即使它们阻止了事件传播_。例如,你可能希望记录每次点击以进行分析,而不管传播逻辑如何。你可以通过在事件名称末尾添加 Capture
来实现这一点。
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
每个事件都会经历三个阶段的传播:
- 它向下传播,调用所有
onClickCapture
处理程序。 - 它运行被点击元素的
onClick
处理程序。 - 它向上传播,调用所有
onClick
处理程序。
捕获事件对于路由器或分析之类的代码很有用,但在应用程序代码中你可能不会使用它们。
将处理程序作为事件传播的替代方案
注意,这个点击处理程序运行一行代码,_然后_调用父组件传递的 onClick
prop。
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
你也可以在这个处理程序中添加更多代码,然后再调用父级 onClick
事件处理程序。此模式提供了一种事件传播的_替代方案_。它允许子组件处理事件,同时还允许父组件指定一些附加行为。与事件传播不同,它不是自动的。但这种模式的好处是,你可以清楚地跟踪由于某些事件而执行的整个代码链。
如果依赖事件传播并且难以追踪哪些处理程序执行以及原因,请尝试这种方法。
阻止默认行为
某些浏览器事件具有与其关联的默认行为。例如,<form>
提交事件(当点击其内部的按钮时发生)默认会重新加载整个页面。
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
你可以在事件对象上调用 e.preventDefault()
来阻止这种情况发生。
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
不要混淆 e.stopPropagation()
和 e.preventDefault()
。它们都有用,但彼此无关。
e.stopPropagation()
阻止附加到上方标签的事件处理程序触发。e.preventDefault()
阻止少数具有默认浏览器行为的事件的默认行为。
事件处理程序可以有副作用吗?
当然可以!事件处理程序是处理副作用的最佳位置。
与渲染函数不同,事件处理程序不需要是纯函数,因此它是更改某些内容的好地方——例如,响应输入更改输入值,或响应按钮点击更改列表。但是,为了更改某些信息,首先需要某种方法来存储它。在 React 中,这是通过使用状态(组件的内存)来完成的。你将在下一页了解所有相关内容。
回顾
- 可以通过将函数作为 prop 传递给像
<button>
这样的元素来处理事件。 - 事件处理程序必须传递,不能调用!
onClick={handleClick}
,而不是onClick={handleClick()}
。 - 可以单独定义事件处理程序函数或内联定义。
- 事件处理程序在组件内部定义,因此它们可以访问 props。
- 可以在父组件中声明事件处理程序,并将其作为 prop 传递给子组件。
- 可以使用具有应用程序特定名称的自定事件处理程序 props。
- 事件向上冒泡。在第一个参数上调用
e.stopPropagation()
以阻止冒泡。 - 事件可能具有不需要的默认浏览器行为。调用
e.preventDefault()
来阻止这种情况。 - 从子处理程序显式调用事件处理程序 prop 是冒泡传播的良好替代方法。
挑战 1的 2: 修复事件处理程序
点击此按钮应该在白色和黑色之间切换页面背景。但是,点击它时没有任何反应。请修复此问题。(不用担心handleClick
内部的逻辑——那部分没问题。)
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> ); }