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> ); }
这使得这两个按钮可以显示不同的消息。尝试更改传递给它们的消息。
将事件处理程序作为 prop 传递
通常,您希望父组件指定子组件的事件处理程序。以按钮为例:根据您使用 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。它使用 onClick={onClick}
将该 prop 直接传递给内置的浏览器 <button>
。这告诉 React 在单击时调用传递的函数。
如果您使用设计系统,则像按钮这样的组件通常包含样式但不指定行为。相反,像 PlayButton
和 UploadButton
这样的组件会向下传递事件处理程序。
命名事件处理程序 prop
像 <button>
和 <div>
这样的内置组件只支持浏览器事件名称,比如 onClick
。但是,当您构建自己的组件时,您可以随意命名其事件处理程序 prop。
按照惯例,事件处理程序 prop 应以 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 名称由您决定!
当您的组件支持多种交互时,您可以为特定于应用程序的概念命名事件处理程序 prop。例如,这个 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
这样的特定于应用程序的交互之后命名 prop,使您能够灵活地更改它们以后的使用方式。
事件传播
事件处理程序还会捕获来自组件可能具有的任何子组件的事件。我们说一个事件“冒泡”或“传播”到树上:它从事件发生的地方开始,然后向上到树。
这个 <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
组件传递的 props。
- 调用
- 该函数在
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
props
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> ); }