如何使用React处理DOM和窗口事件
作为 Write for DOnations 计划的一部分,作者选择了 Creative Commons 来接受捐赠。
介绍
在 Web 开发中,events 表示在 Web 浏览器中发生的动作。 通过使用 事件处理程序 响应事件,您可以创建动态的 JavaScript 应用程序来响应任何用户操作,包括用鼠标单击、沿网页滚动、触摸触摸屏以及更多的。
在 React 应用程序中,您可以使用事件处理程序来更新 状态数据、触发 prop 更改或阻止默认浏览器操作。 为此,React 使用 SyntheticEvent
包装器而不是原生的 Event 接口。 SyntheticEvent
密切模拟标准浏览器事件,但为不同的 Web 浏览器提供更一致的行为。 当组件从 文档对象模型 (DOM) 挂载和卸载时,React 还为您提供了安全添加和删除 Window 事件侦听器的工具,让您可以控制 [X197X ] 事件,同时防止 内存泄漏 来自不正确删除的侦听器。
在本教程中,您将学习如何在 React 中处理事件。 您将构建几个处理用户事件的示例组件,包括一个自我验证的输入组件和一个用于输入表单的信息工具提示。 在整个教程中,您将学习如何向组件添加事件处理程序、从 SyntheticEvent
中提取信息以及添加和删除 Window
事件侦听器。 在本教程结束时,您将能够使用各种事件处理程序并应用 React 支持的 事件目录。
先决条件
- 您将需要一个运行 Node.js 的开发环境; 本教程在 Node.js 版本 10.22.0 和 npm 版本 6.14.6 上进行了测试。 要在 macOS 或 Ubuntu 18.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 18.04 上安装 Node.js。
- 使用 Create React App 设置的 React 开发环境,删除了非必要的样板。 要进行此设置,请按照 步骤 1 — 创建一个空项目的如何管理 React 类组件上的状态教程 。 本教程将使用
events-tutorial
作为项目名称。 - 您还需要 JavaScript 和 HTML 的基本知识,您可以在我们的 如何使用 HTML 构建网站系列 和 如何在 JavaScript 中编码 中找到这些知识。 CSS 的基本知识也会很有用,您可以在 Mozilla 开发者网络 上找到这些知识。
- 您将使用 React 组件、
useState
Hook 和useReducer
Hook,您可以在我们的教程 How To Create Custom Components in React 和 中了解它们]如何使用 React 组件上的钩子管理状态。
第 1 步 — 使用 SyntheticEvent
提取事件数据
在此步骤中,您将使用 <input>
HTML 元素和 onChange
事件处理程序创建一个验证组件。 该组件将接受输入并 validate 它,或确保内容符合特定的文本模式。 您将使用 SyntheticEvent
包装器将事件数据传递到 回调函数 并使用来自 <input>
的数据更新组件。 您还将从 SyntheticEvent
调用 函数,例如 preventDefault
以阻止标准浏览器操作。
在 React 中,您不需要在添加事件侦听器之前选择元素。 相反,您可以使用道具将事件处理程序直接添加到您的 JSX 中。 React中有大量支持的事件,包括onClick
或onChange
等常见事件和onWheel
等不太常见的事件。
与原生 DOM 事件处理程序 不同,React 将一个名为 SyntheticEvent
的特殊包装器传递给事件处理程序,而不是原生浏览器 Event
。 抽象有助于减少跨浏览器的不一致,并为您的组件提供处理事件的标准接口。 SyntheticEvent
的 API 与原生的 Event
类似,因此大多数任务都以相同的方式完成。
为了证明这一点,您将从进行验证输入开始。 首先,您将创建一个名为 FileNamer
的组件。 这将是一个带有用于命名文件的输入的 <form>
元素。 当您填写输入内容时,您将看到信息更新组件上方的预览框。 该组件还将包括一个提交按钮来运行验证,但对于本示例,表单实际上不会提交任何内容。
首先,创建目录:
mkdir src/components/FileNamer
然后在文本编辑器中打开 FileNamer.js
:
nano src/components/FileNamer/FileNamer.js
在 FileNamer.js
中,创建一个包装器 <div>
,然后在包装器中添加另一个 <div>
,其类名为 preview
和一个 <form>
元素通过编写以下代码行:
事件教程/src/components/FileNamer/FileNamer.js
import React from 'react'; export default function FileNamer() { return( <div className="wrapper"> <div className="preview"> </div> <form> </form> </div> ) }
接下来,为要在预览框中显示的名称添加一个输入元素,并添加一个 Save 按钮。 添加以下突出显示的行:
事件教程/src/components/FileNamer/FileNamer.js
import React from 'react'; export default function FileNamer() { return( <div className="wrapper"> <div className="preview"> <h2>Preview:</h2> </div> <form> <label> <p>Name:</p> <input name="name" /> </label> <div> <button>Save</button> </div> </form> </div> ) }
在 preview
<div>
中,您添加了一个带有文本 Preview
的 <h2>
元素。 这将是您的预览框。 在您的表单中,您添加了一个 <input>
,它被一个 <label>
元素包围,并以 Name:
作为其文本。 然后,您在结束 <form>
标记之前直接添加了一个名为 Save 的 button
。
保存并关闭文件。
接下来,打开App.js
:
nano src/components/App/App.js
导入 FileNamer
,然后通过添加以下突出显示的行在 App
函数内渲染:
事件教程/src/components/App/App.js
import React from 'react'; import FileNamer from '../FileNamer/FileNamer'; function App() { return <FileNamer /> } export default App;
保存并关闭文件。 当您这样做时,浏览器将刷新,您将看到您的组件。
接下来,添加一些浅色样式以帮助定义部分并为元素添加一些填充和边距。
在文本编辑器中打开 FileNamer.css
:
nano src/components/FileNamer/FileNamer.css
给 .preview
类一个灰色边框和填充,然后给 .wrapper
类少量填充。 使用 flex 和 flex-direction
显示列中的项目,并使所有文本左对齐。 最后,通过移除边框并添加黑色边框来移除默认按钮样式:
事件教程/src/components/FileNamer/FileNamer.css
.preview { border: 1px darkgray solid; padding: 10px; } .wrapper { display: flex; flex-direction: column; padding: 20px; text-align: left; } .wrapper button { background: none; border: 1px black solid; margin-top: 10px; }
保存并关闭文件。 然后打开FileNamer.js
:
nano src/components/FileNamer/FileNamer.js
导入样式以将它们应用到您的组件:
事件教程/src/components/FileNamer/FileNamer.js
import React from 'react'; import './FileNamer.css'; export default function FileNamer() { return( <div className="wrapper"> <div className="preview"> <h2>Preview:</h2> </div> <form> <label> <p>Name:</p> <input name="name" /> </label> <div> <button>Save</button> </div> </form> </div> ) }
保存文件。 当您这样做时,浏览器将刷新,您会发现组件具有新样式。
现在您已经有了一个基本组件,您可以将事件处理程序添加到 <input>
元素。 但首先,您需要在输入字段中存储数据的地方。 添加 useState Hook 来保存输入:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input name="name" /> </label> <div> <button>Save</button> </div> </form> </div> ) }
在此代码中,您将 useState
解构为一个变量 name
来保存输入和一个名为 setName
的函数来更新数据。 然后在预览部分显示 name
后跟 .js
扩展名,就像用户在命名文件一样。
现在您可以存储输入数据,您可以将事件处理程序添加到 <input>
组件。 通常有几个不同的事件处理程序可以用于给定任务。 在这种情况下,您的应用程序需要捕获用户在元素中键入的数据。 这种情况最常见的处理程序是 onChange
,每次组件更改时都会触发。 但是,您也可以使用 键盘事件 ,例如 onKeyDown
、onKeyPress
和 onKeyUp
。 区别主要与事件触发的时间和传递给 SyntheticEvent
对象的信息有关。 例如,onBlur
,当元素变得未聚焦时的事件,在 onClick
之前触发。 如果您想在另一个事件触发之前处理用户信息,您可以选择一个较早的事件。
您对事件的选择还取决于您要传递给 SyntheticEvent
的数据类型。 例如,onKeyPress
事件将包括用户按下的键的 charCode
,而 onChange
将不包括特定的字符代码,但将包括完整的输入. 如果您想根据用户按下的键执行不同的操作,这一点很重要。
对于本教程,使用 onChange
来捕获整个输入值,而不仅仅是最近的键。 这将节省您在每次更改时存储和连接值的工作。
创建一个将 event
作为参数的函数,并使用 onChange
属性将其传递给 <input>
元素:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input name="name" onChange={event => {}}/> </label> <div> <button>Save</button> </div> </form> </div> ) }
前面说过,这里的event
并不是浏览器原生事件。 就是 React 提供的 SyntheticEvent
,经常被这样对待。 在极少数情况下您需要原生事件,您可以使用 SyntheticEvent
上的 nativeEvent
属性。
现在您有了事件,从事件的 target.value
属性中提取当前值。 将值传递给 setName
以更新预览:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autoComplete="off" name="name" onChange={event => setName(event.target.value) } /> </label> <div> <button>Save</button> </div> </form> </div> ) }
此外,您将属性 autoComplete 设置为 "off"
以关闭浏览器建议。
保存文件。 当您这样做时,页面将重新加载,当您输入 <input>
时,您将在预览中看到更新。
注意: 您也可以使用 event.target.name
访问输入的名称。 如果您在多个输入中使用相同的事件处理程序,这将很有用,因为 name
将自动匹配组件的 name
属性。
此时,您有一个有效的事件处理程序。 您正在获取用户信息,将其保存到状态,并使用数据更新另一个组件。 但除了从事件中提取信息之外,在某些情况下您还需要停止事件,例如,如果您想阻止表单提交或阻止按键操作。
要停止事件,请对事件调用 preventDefault
操作。 这将阻止浏览器执行默认行为。
在 FileNamer
组件的情况下,某些字符可能会破坏选择应用程序应禁止的文件的过程。 例如,您不希望用户将 *
添加到文件名,因为它与通配符冲突,通配符可能被解释为引用不同的文件集。 在用户提交表单之前,您需要检查以确保没有无效字符。 如果存在无效字符,您将阻止浏览器提交表单并为用户显示一条消息。
首先,创建一个将生成 alert
boolean 和 setAlert
函数的 Hook。 如果 alert
为真,则添加 <div>
以显示消息:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autoComplete="off" name="name" onChange={event => setName(event.target.value) } /> </label> {alert && <div> Forbidden Character: *</div>} <div> <button>Save</button> </div> </form> </div> ) }
在此代码中,如果首先将 alert
设置为等于 true
,则使用 &&
运算符仅显示新的 <div>
。 <div>
中的消息将告诉用户输入中不允许使用 *
字符。
接下来,创建一个名为 validate
的函数。 使用正则表达式 .test method来判断字符串是否包含*
。 如果是这样,您将阻止表单提交:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); const validate = event => { if(/\*/.test(name)) { event.preventDefault(); setAlert(true); return; } setAlert(false); }; return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autoComplete="off" name="name" onChange={event => setName(event.target.value) } /> </label> {alert && <div> Forbidden Character: *</div>} <div> <button onClick={validate}>Save</button> </div> </form> </div> ) }
当调用 validate
函数并且测试返回 true
时,它将使用 event.preventDefault
然后调用 setAlert(true)
。 否则,它将调用 setAlert(false)
。 在代码的最后一部分,您使用 onClick
将事件处理程序添加到 <button>
元素。
保存文件。 和以前一样,您也可以使用 onMouseDown
,但 onClick
更常见,因此可以避免任何意外的副作用。 此表单没有任何提交操作,但通过阻止默认操作,您可以阻止页面重新加载:
现在您有一个使用两个事件处理程序的表单:onChange
和 onClick
。 您正在使用事件处理程序将用户操作连接到组件和应用程序,使其具有交互性。 在此过程中,您学会了向 DOM 元素添加事件,以及如何在同一个动作上触发多个事件,但在 SyntheticEvent
中提供不同的信息。 您还学习了如何从 SyntheticEvent
中提取信息,通过将数据保存到状态来更新其他组件,以及使用 preventDefault
停止事件。
在下一步中,您将向单个 DOM 元素添加多个事件以处理各种用户操作。
第 2 步 — 将多个事件处理程序添加到同一元素
在某些情况下,单个组件会触发多个事件,您需要能够连接到单个组件上的不同事件。 例如,在此步骤中,您将使用 onFocus
和 onBlur
事件处理程序为用户提供有关组件的即时信息。 在这一步结束时,您将了解更多关于 React 中支持的不同事件以及如何将它们添加到您的组件中。
validate
功能有助于防止表单提交不良数据,但对用户体验帮助不大:用户只有在填写完整个表单后才会收到有关有效字符的信息。 如果有多个字段,直到最后一步才会给用户任何反馈。 为了让这个组件对用户更加友好,当用户输入字段时,通过添加一个 onFocus
事件处理程序来显示允许和不允许的字符。
首先,更新 alert
<div>
以包含有关允许使用哪些字符的信息。 告诉用户允许使用字母数字字符并且不允许使用 *
:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { ... return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autocomplete="off" name="name" onChange={event => setName(event.target.value) } /> </label> {alert && <div> <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters <br /> <span role="img" aria-label="not allowed">⛔️</span> * </div> } <div> <button onClick={validate}>Save</button> </div> </form> </div> ) }
在此代码中,您使用 Accessible Rich Internet Applications (ARIA) 标准 使屏幕阅读器更易于访问组件。
接下来,将另一个事件处理程序添加到 <input>
元素。 当用户通过单击或切换输入来激活组件时,您将提醒用户允许和不允许的字符。 添加以下突出显示的行:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { ... return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autocomplete="off" name="name" onChange={event => setName(event.target.value) } onFocus={() => setAlert(true)} /> </label> {alert && <div> <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters <br /> <span role="img" aria-label="not allowed">⛔️</span> * </div> } <div> <button onClick={validate}>Save</button> </div> </form> </div> ) }
您将 onFocus
事件处理程序添加到 <input>
元素。 此事件在用户选择字段时触发。 添加事件处理程序后,您向 onFocus
传递了一个匿名函数,该函数将调用 setAlert(true)
并显示数据。 在这种情况下,您不需要来自 SyntheticEvent
的任何信息; 您只需要在用户动作时触发一个事件。 React 仍在将 SyntheticEvent
发送给函数,但在当前情况下,您不需要使用其中的信息。
注意: 您可以使用 onClick
甚至 onMouseDown
来触发数据显示,但是对于使用键盘 Tab 进入表单字段的用户来说,这是无法访问的。 在这种情况下,onFocus
事件将处理这两种情况。
保存文件。 当您这样做时,浏览器将刷新并且信息将保持隐藏,直到用户单击输入。
现在,当该字段获得焦点时会显示用户信息,但现在数据在组件的持续时间内存在。 没有办法让它消失。 幸运的是,还有一个名为 onBlur
的事件会在用户离开输入时触发。 添加带有匿名函数的 onBlur
事件处理程序,该函数将 alert
设置为 false
。 像 onFocus
一样,当用户点击离开或用户离开时,这将起作用:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { ... return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autocomplete="off" name="name" onBlur={() => setAlert(false)} onChange={event => setName(event.target.value) } onFocus={() => setAlert(true)} /> </label> {alert && <div> <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters <br /> <span role="img" aria-label="not allowed">⛔️</span> * </div> } <div> <button onClick={validate}>Save</button> </div> </form> </div> ) }
保存文件。 当你这样做时,浏览器将刷新,当用户点击元素时信息将显示,当用户点击离开时信息将消失:
您可以根据需要向元素添加任意数量的事件处理程序。 如果您对需要的事件有所了解,但不确定名称,请滚动浏览 支持的事件,您可能会找到所需的内容。
在此步骤中,您将多个事件处理程序添加到单个 DOM 元素。 您了解了不同的事件处理程序如何处理范围广泛的事件(例如单击和选项卡)或范围较窄的事件。
在下一步中,您将向 Window
对象添加全局事件侦听器,以捕获在直接组件之外发生的事件。
第三步——添加窗口事件
在此步骤中,您将把用户信息放在一个弹出组件中,当用户聚焦输入时该组件将激活,当用户单击页面上的其他任何位置时该组件将关闭。 为实现此效果,您将使用 useEffect Hook 将全局事件侦听器添加到 Window 对象。 当您的应用程序占用的内存超出其需要时,您还将在组件卸载以防止内存泄漏时删除事件侦听器。
在这一步结束时,您将能够安全地在各个组件上添加和删除事件侦听器。 您还将学习如何使用 useEffect
Hook 在组件安装和卸载时执行操作。
在大多数情况下,您将直接将事件处理程序添加到 JSX 中的 DOM 元素。 这使您的代码保持专注,并防止组件通过 Window
对象控制另一个组件的行为的混乱情况。 但有时您需要添加全局事件侦听器。 例如,您可能希望滚动侦听器加载新内容,或者您可能希望捕获组件外部的点击事件。
在本教程中,您只想在用户特别要求时向用户显示有关输入的信息。 显示信息后,您需要在用户单击组件外部的页面时将其隐藏。
首先,将 alert
显示移动到具有 information-wrapper
的 className
的新 <div>
中。 然后添加一个新按钮,其中包含 information
的 className
和将调用 setAlert(true)
的 onClick
事件:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { ... return( <div className="wrapper"> <div className="preview"> <h2>Preview: {name}.js</h2> </div> <form> <label> <p>Name:</p> <input autocomplete="off" name="name" onChange={event => setName(event.target.value) } /> </label> <div className="information-wrapper"> <button className="information" onClick={() => setAlert(true)} type="button" > more information </button> {alert && <div className="popup"> <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters <br /> <span role="img" aria-label="not allowed">⛔️</span> * </div> } </div> <div> <button onClick={validate}>Save</button> </div> </form> </div> ) }
您还从 <input>
元素中删除了 onFocus
和 onBlur
处理程序,以删除最后一步的行为。
保存并关闭文件。 然后打开FileNamer.css
:
nano src/components/FileNamer/FileNamer.css
添加一些样式以绝对定位按钮上方的 popup
信息。 然后将具有information
类的<button>
更改为无边框的蓝色。
事件教程/src/components/FileNamer/FileNamer.css
.information { font-size: .75em; color: blue; cursor: pointer; } .wrapper button.information { border: none; } .information-wrapper { position: relative; } .popup { position: absolute; background: white; border: 1px darkgray solid; padding: 10px; top: -70px; left: 0; } .preview { border: 1px darkgray solid; padding: 10px; } .wrapper { display: flex; flex-direction: column; padding: 20px; text-align: left; } .wrapper button { background: none; border: 1px black solid; margin-top: 10px; }
保存并关闭文件。 当您这样做时,浏览器将重新加载,当您单击 more information
时,将出现有关组件的信息:
现在您可以触发弹出窗口,但无法清除它。 要解决该问题,请添加一个全局事件侦听器,该侦听器在弹出窗口之外的任何单击时调用 setAlert(false)
。
事件监听器看起来像这样:
window.addEventListener('click', () => setAlert(false))
但是,您必须注意何时在代码中设置事件侦听器。 例如,您不能在组件代码的顶部添加事件侦听器,因为每次发生更改时,组件都会重新渲染并添加新的事件侦听器。 由于您的组件可能会重新渲染很多次,这将创建许多占用内存的未使用的事件侦听器。
为了解决这个问题,React 有一个称为 useEffect
的特殊 Hook,它仅在特定属性更改时运行。 基本结构是这样的:
useEffect(() => { // run code when anything in the array changes }, [someProp, someOtherProp])
在简化的示例中,只要 someProp
或 someOtherProp
发生变化,React 就会运行匿名函数中的代码。 数组中的项称为 dependencies。 该 Hook 监听依赖项的更改,然后在更改后运行该函数。
现在,您可以使用 useEffect
在 alert
为 true
时添加事件监听器并在 alert
时删除它来安全地添加和删除全局事件监听器的工具] 是 false
。
还有一步。 当组件卸载时,它将运行您从 useEffect
Hook 内部返回的任何函数。 因此,您还需要返回一个在组件卸载时删除事件侦听器的函数。
基本结构是这样的:
useEffect(() => { // run code when anything in the array changes return () => {} // run code when the component unmounts }, [someProp, someOtherProp])
现在您知道 useEffect
钩子的形状,在您的应用程序中使用它。 打开FileNamer.js
:
nano src/components/FileNamer/FileNamer.js
里面,导入useEffect
,然后在函数后面的数组中添加一个空匿名函数,依赖为alert
和setAlert
:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useEffect, useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); useEffect(() => { }, [alert, setAlert]); ...
在此代码中,您添加了 alert
和 setAlert
。 为了完整起见,React 建议您将所有外部依赖项添加到 useEffect
函数中。 由于您将调用 setAlert
函数,因此可以将其视为依赖项。 setAlert
在第一次渲染后不会改变,但最好包含任何可能被视为依赖项的内容。
接下来,在匿名函数中,创建一个名为 handleWindowClick
的新函数,该函数调用 setAlert(false)
:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useEffect, useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); useEffect(() => { const handleWindowClick = () => setAlert(false) }, [alert, setAlert]); ... }
然后添加一个 conditional,当 alert
为 true
时将调用 window.addEventListener('click', handleWindowClick)
,当 alert
为时将调用 window.removeEventListener('click', handleWindowClick)
false
。 这将在每次触发弹出窗口时添加事件侦听器,并在每次弹出窗口关闭时将其删除:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useEffect, useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); useEffect(() => { const handleWindowClick = () => setAlert(false) if(alert) { window.addEventListener('click', handleWindowClick); } else { window.removeEventListener('click', handleWindowClick); } }, [alert, setAlert]); ... }
最后,返回一个将删除事件侦听器的函数。 再次,这将在组件卸载时运行。 可能没有实时事件侦听器,但在侦听器仍然存在的情况下仍然值得清理:
事件教程/src/components/FileNamer/FileNamer.js
import React, { useEffect, useState } from 'react'; import './FileNamer.css'; export default function FileNamer() { const [name, setName] = useState(''); const [alert, setAlert] = useState(false); useEffect(() => { const handleWindowClick = () => setAlert(false) if(alert) { window.addEventListener('click', handleWindowClick); } else { window.removeEventListener('click', handleWindowClick) } return () => window.removeEventListener('click', handleWindowClick); }, [alert, setAlert]); ... }
保存文件。 当你这样做时,浏览器将刷新。 如果单击 更多信息 按钮,将出现该消息。 如果您查看开发人员工具中的全局事件侦听器,您会看到有一个 click
侦听器:
单击组件外的任意位置。 该消息将消失,您将不再看到全局单击事件侦听器。
您的 useEffect
Hook 基于用户交互成功添加和删除了全局事件侦听器。 它没有绑定到特定的 DOM 元素,而是由组件状态的更改触发。
注意: 从可访问性的角度来看,这个组件是不完整的。 如果用户不能使用鼠标,他们将被一个打开的弹出窗口卡住,因为他们永远无法在组件外部单击。 解决方案是为 keydown
添加另一个事件侦听器,该侦听器也会删除该消息。 代码几乎相同,只是方法是 keydown
而不是 click
。
在这一步中,您在组件中添加了全局事件侦听器。 您还学习了如何使用 useEffect
Hook 在状态更改时正确添加和删除事件侦听器,以及如何在组件卸载时清理事件侦听器。
结论
事件处理程序使您有机会将组件与用户操作对齐。 这些将为您的应用程序提供丰富的体验,并将增加您的应用程序的交互可能性。 它们还将使您能够捕获和响应用户操作。
React 的事件处理程序允许您将事件回调与 HTML 集成,以便您可以跨应用程序共享功能和设计。 在大多数情况下,您应该专注于将事件处理程序直接添加到 DOM 元素,但在需要捕获组件之外的事件的情况下,您可以添加事件侦听器并在不再使用时清理它们以防止内存泄漏并创建性能应用程序。
如果您想查看更多 React 教程,请查看我们的 React 主题页面,或返回 如何在 React.js 系列页面中编码 。 要了解有关 JavaScript 中事件的更多信息,请阅读我们的 Understanding Events in JavaScript 和 Using Event Emitters in Node.js 教程。