响应事件
React 允许你向 JSX 添加事件处理函数。事件处理函数是你自己的函数,它会在用户交互时被触发,例如点击、悬停、聚焦表单输入等。
您将学习
- 编写事件处理函数的不同方式
- 如何从父组件传递事件处理逻辑
- 事件如何传播以及如何阻止它们
添加事件处理函数
要添加一个事件处理函数,你需要先定义一个函数,然后将其作为 prop 传递给相应的 JSX 标签。例如,下面是一个目前什么都不做的按钮:
你可以通过以下三个步骤使其在用户点击时显示一条消息:
- 在你的
Button组件内部声明一个名为handleClick的函数。 - 在该函数内部实现逻辑(使用
alert来显示消息)。 - 向
<button>JSX 添加onClick={handleClick}。
你定义了handleClick函数,然后将其作为 prop 传递给了<button>。handleClick是一个事件处理函数。事件处理函数:
- 通常定义在组件内部。
- 名称以
handle开头,后接事件名称。
按照惯例,通常将事件处理函数命名为handle后接事件名称。你经常会看到onClick={handleClick}、onMouseEnter={handleMouseEnter}等等。
或者,你也可以在 JSX 中内联定义事件处理函数:
或者,使用箭头函数更简洁地编写:
这些写法都是等效的。内联事件处理函数对于短函数来说很方便。
常见陷阱
传递给事件处理器的函数必须是传递,而不是调用。例如:
| 传递函数(正确) | 调用函数(错误) |
|---|---|
<button onClick={handleClick}> | <button onClick={handleClick()}> |
区别很微妙。在第一个例子中,handleClick 函数作为 onClick事件处理器被传递。这告诉 React 记住它,并且只在用户点击按钮时调用你的函数。
在第二个例子中,handleClick() 末尾的 () 会在 立即执行该函数,而无需任何点击。这是因为渲染 过程中 JSX { 和 }中的 JavaScript 会立即执行。
当你编写内联代码时,同样的陷阱会以不同的方式出现:
| 传递函数(正确) | 调用函数(错误) |
|---|---|
<button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
像这样传递内联代码不会在点击时触发——它会在每次组件渲染时触发:
如果你想内联定义事件处理器,请将其包装在一个匿名函数中,如下所示:
这样不会在每次渲染时执行其中的代码,而是创建一个稍后调用的函数。
在这两种情况下,你想要传递的都是一个函数:
<button onClick={handleClick}>传递了handleClick函数。<button onClick={() => alert('...')}>传递了() => alert('...')函数。
在事件处理程序中读取 props
由于事件处理程序是在组件内部声明的,因此它们可以访问组件的 props。这是一个按钮,点击时会显示一个包含其messageprop 的警告框:
这样可以让这两个按钮显示不同的消息。尝试修改传递给它们的消息。
将事件处理函数作为 props 传递
通常你会希望父组件来指定子组件的事件处理函数。以按钮为例:根据你使用 Button组件的位置,你可能希望执行不同的函数——也许一个播放电影,另一个上传图片。
为此,将组件从其父组件接收到的 prop 作为事件处理函数传递,如下所示:
这里,Toolbar组件渲染了一个PlayButton和一个UploadButton:
PlayButton将handlePlayClick作为onClickprop 传递给内部的Button。UploadButton将() => alert('Uploading!')作为onClickprop 传递给内部的Button。
最后,你的 Button 组件接收一个名为 onClick的 prop。它将该 prop 直接传递给内置的浏览器<button>,使用onClick={onClick}。这告诉 React 在点击时调用传递的函数。
如果你使用设计系统,像按钮这样的组件通常包含样式但不指定行为。相反,像 PlayButton 和 UploadButton这样的组件会将事件处理函数向下传递。
事件处理函数 prop 的命名
像 <button> 和 <div>这样的内置组件只支持浏览器事件名称,例如onClick。然而,当你构建自己的组件时,你可以随意命名它们的事件处理函数 prop。
按照惯例,事件处理函数 prop 应以on开头,后跟一个大写字母。
例如,Button 组件的 onClickprop 本可以命名为onSmash:
在这个例子中,<button onClick={onSmash}> 表明浏览器 <button>(小写)仍然需要一个名为onClick 的 prop,但你的自定义 Button组件接收的 prop 名称由你决定!
当你的组件支持多种交互时,你可能会根据应用特定的概念来命名事件处理函数 prop。例如,这个Toolbar 组件接收 onPlayMovie 和 onUploadImage事件处理函数:
请注意,App 组件不需要知道 具体Toolbar将如何处理onPlayMovie 或 onUploadImage。这是 Toolbar的实现细节。在这里,Toolbar 将它们作为 onClick处理函数传递给其Button 组件,但之后也可能通过键盘快捷键来触发它们。根据应用特定的交互(如 onPlayMovie)来命名属性,可以让你在后续灵活地改变它们的使用方式。
注意
请确保为事件处理函数使用合适的 HTML 标签。例如,要处理点击事件,请使用<button onClick={handleClick}>,而不是<div onClick={handleClick}>。使用真正的浏览器<button>可以启用内置的浏览器行为,如键盘导航。如果你不喜欢按钮的默认浏览器样式,并希望它看起来更像链接或其他 UI 元素,可以通过 CSS 来实现。了解更多关于编写无障碍标记的信息。
事件传播
事件处理函数也会捕获组件可能拥有的任何子组件的事件。我们说事件会“冒泡”或“传播”到树的上层:它从事件发生的地方开始,然后沿着树向上传播。
这个<div> 包含两个按钮。<div>和每个按钮都有各自的onClick处理函数。当你点击一个按钮时,你认为哪些处理函数会被触发?
如果你点击任意一个按钮,它的onClick 会首先执行,然后是父级 <div>的onClick。因此会显示两条消息。如果你点击工具栏本身,则只有父级<div>的onClick会执行。
陷阱
在 React 中,除了onScrollonScroll 之外的所有事件都会传播,而 仅作用于你附加它的 JSX 标签。
阻止传播
事件处理函数接收一个事件对象作为唯一的参数。按照惯例,它通常被称为e,代表“event”(事件)。你可以使用这个对象来读取事件的相关信息。
该事件对象也允许你阻止传播。如果你想阻止事件到达父组件,你需要像下面这个 Buttone.stopPropagation()组件一样调用:
当你点击一个按钮时:
- React 调用传递给
onClick的处理函数。 - 该处理函数在
Button组件中定义,执行以下操作:- 调用
e.stopPropagation(),阻止事件进一步冒泡。 - 调用
onClick函数,该函数是从Toolbar组件传递下来的 prop。
- 调用
- 该函数在
Toolbar组件中定义,显示按钮自身的警告框。 - 由于传播被阻止,父级
<div>的onClick处理函数不会运行。
由于 e.stopPropagation()的作用,现在点击按钮只会显示一个警告框(来自<button>),而不是两个(分别来自<button> 和父级工具栏的 <div>)。点击按钮与点击其周围的工具栏是不同的操作,因此对于这个 UI 来说,阻止传播是合理的。
将处理函数作为传播的替代方案传递
请注意,这个点击处理函数如何运行一行代码然后调用父组件传递的onClick 属性:
您也可以在此处理函数中调用父组件 onClick事件处理函数之前添加更多代码。这种模式提供了一种传播的替代方案。它让子组件处理事件,同时也让父组件指定一些额外的行为。与传播不同,它不是自动的。但这种模式的好处是,您可以清晰地跟踪因某个事件而执行的整个代码链。
如果您依赖传播,并且很难追踪哪些处理函数执行以及为什么执行,请尝试改用这种方法。
阻止默认行为
某些浏览器事件具有与之关联的默认行为。例如,<form> 提交事件(当其中的按钮被点击时发生)默认会重新加载整个页面:
你可以在事件对象上调用e.preventDefault() 来阻止这种情况发生:
不要混淆 e.stopPropagation() 和 e.preventDefault()。它们都很有用,但彼此无关:
- e.stopPropagation()会阻止附加在上层标签上的事件处理函数触发。
- e.preventDefault()会阻止少数具有默认行为的浏览器事件执行其默认行为。
事件处理函数可以有副作用吗?
当然可以!事件处理函数是产生副作用的最佳场所。
与渲染函数不同,事件处理函数不需要是纯函数,因此它是改变某些东西的理想位置——例如,根据输入内容改变输入框的值,或者根据按钮点击改变列表。然而,为了改变某些信息,你首先需要某种方式来存储它。在 React 中,这是通过使用状态(组件的记忆)来实现的。你将在下一页学到所有相关内容。
回顾
- 你可以通过将函数作为 prop 传递给像
<button>这样的元素来处理事件。 - 事件处理函数必须被传递,而不是被调用!
onClick={handleClick},而不是onClick={handleClick()}。 - 你可以单独定义事件处理函数,也可以内联定义。
- 事件处理函数在组件内部定义,因此可以访问 props。
- 你可以在父组件中声明事件处理函数,并将其作为 prop 传递给子组件。
- 你可以定义具有特定应用名称的自定义事件处理函数 prop。
- 事件会向上传播。在第一个参数上调用
e.stopPropagation()可以阻止传播。 - 事件可能具有不需要的默认浏览器行为。调用
e.preventDefault()可以阻止该行为。 - 从子组件的事件处理函数中显式调用父组件传递的事件处理函数 prop,是替代事件传播的一种好方法。
