编写我的第一个自定义 React Hook——useOutsideClick
让我们开始吧
React Hooks 的推出彻底改变了 React 生态系统。我使用 React Hooks 已经有一段时间了,而且非常喜欢它。但和很多其他开发者一样,我从未编写过自定义的 React Hook。这主要是因为:首先,我需要的所有功能都可以在第三方 Hooks 库中找到;其次,我比较拖延。
我坚信实践出真知。因此,我将创建一个非常简单的钩子函数——useOutsideClick。这个钩子函数可以帮助我们在用户点击组件外部时触发一个函数。
我们可以在哪里使用它?
- 当用户点击组件外部时,关闭组件的展开状态。
- 当用户点击模态框外部时,关闭模态框。
以及更多
我们将如何实现这一点?
这可能不是最佳方法,但我在之前的基于类的组件中一直使用一种非常简单的方法。我将尝试用自定义钩子来复现这种方法。具体步骤如下:
- 我们将
onClickListener在document组件安装时添加一个。 - 在这个点击监听器中,
outsideClickHandler当点击目标位于所需组件之外时,我们将触发该监听器。
让我们开始吧
您可以在此GitHub 仓库中找到本教程的最终代码,并在此处找到一个可运行的在线演示。
让我们创建一个 React 应用,并使用以下命令运行它。
npx create-react-app useOutsideClick
npm install # to install all dependencies
npm run start # to run the app
我们首先会在一个简单的函数组件中创建外部点击功能,然后尝试将其提取到一个自定义钩子中。
让我们编辑src/App.js成这样:
import "./styles.css";
export default function App() {
return (
<div className="App">
<div className="main">Click me</div>
</div>
);
}
并更新样式./styles.css,使其不那么难看。
html, body, #root {
display: grid;
place-items: center;
height: 100%;
width: 100%;
}
.main {
background: lightskyblue;
font-size: 2rem;
width: 20vh;
height: 10vh;
display: grid;
place-items: center;
border-radius: 40px;
}
如果你查看浏览器,你会看到类似这样的内容:
添加外部点击功能
现在我们将尝试使用useEffect和useRef钩子来检测用户何时点击了显示“点击我”的 div 元素之外的区域。
我们将首先创建一个新的ref外部组件<div>,用于检测点击事件。
const mainRef = useRef();
并将其作为refprop 传递给 div 元素。
<div className="main" ref={mainRef}>
在点击事件处理程序中,我们将检查点击event.target位置是否位于目标元素内部。我们可以使用 `getClick()`contains函数来实现这一点。目前,我们只需记录点击位置是否位于元素外部即可。
const onOutsideClick = (e) => {
const inMain = mainRef.current.contains(e.target);
const isOutside = !inMain;
if (isOutside) {
# call the outside click handler here
console.log("Clicked ouside");
}
};
我们希望在组件挂载时或引用发生变化时,监听整个文档上的点击事件。我们将使用useEffect钩子来实现这一点。
useEffect(() => {
document.addEventListener("click", onOutsideClick);
// cleaning up the event listener when the component unmounts
return () => {
document.removeEventListener("click", onOutsideClick);
};
}, [mainRef]);
我们的src/App.js未来将如同:
import { useEffect, useRef } from "react";
import "./styles.css";
export default function App() {
const mainRef = useRef();
const onOutsideClick = (e) => {
const inMain = mainRef.current.contains(e.target);
const isOutside = !inMain;
if (isOutside) {
console.log("Clicked ouside");
}
};
useEffect(() => {
document.addEventListener("click", onOutsideClick);
return () => {
console.log("cleanup");
document.removeEventListener("click", onOutsideClick);
};
}, [mainRef]);
return (
<div className="App">
<div className="main" ref={mainRef}>
Click me
</div>
</div>
);
}
就这样。现在我们只需要将这个功能提取到一个自定义钩子中即可。
创建自定义钩子
创建一个名为 . 的新文件useOutsideClick.js。现在我们将把代码从我们的src/App.js文件中复制到src/useOutsideClick.js并更新它以接受componentRef和 。outsideClickHandler
# src/useOutsideClick.js
import { useEffect } from "react";
export const useOutsideClick = (componentRef, outsideClickHandler) => {
const onOutsideClick = (e) => {
// updated this to use the passed componentRef
if (!componentRef.current) {
return;
}
const inMain = componentRef.current.contains(e.target);
const isOutside = !inMain;
if (isOutside) {
outsideClickHandler();
}
};
useEffect(() => {
document.addEventListener("click", onOutsideClick);
return () => {
console.log("cleanup");
document.removeEventListener("click", onOutsideClick);
};
}, [componentRef]);
};
现在我们将在应用程序中使用它。
#src/App.js
import { useEffect, useRef } from "react";
import "./styles.css";
import { useOutsideClick } from "./useOutsideClick";
export default function App() {
const mainRef = useRef();
useOutsideClick(mainRef, () => console.log("Clicked outside"));
return (
<div className="App">
<div className="main" ref={mainRef}>
Click me
</div>
</div>
);
}
一切都完美运行🎉
例子
现在我们将更新应用程序以展示其中一个用例。当用户点击蓝色按钮时<div>,按钮下方将显示更多内容。当用户点击屏幕上该按钮以外的任何位置时,这些内容将隐藏。我们将此状态保存在状态变量中。expanded
#src/App.js
import { useEffect, useRef, useState } from "react";
import "./styles.css";
import { useOutsideClick } from "./useOutsideClick";
export default function App() {
const mainRef = useRef();
// initially not expanded
const [expanded, setExpanded] = useState(false);
// set `expanded` to `false` when clicked outside the <div>
useOutsideClick(mainRef, () => setExpanded(false));
return (
<div className="App">
// set `expanded` to `true` when this <div> is clicked
<div className="main" ref={mainRef} onClick={() => setExpanded(true)}>
Click me
</div>
// show more details only when `expanded` is `true`
{expanded && <div className="more">Lorem ipsum dolor sit amet</div>}
</div>
);
}
/* src/styles.css */
/* add this */
.more {
text-align: center;
font-size: 1.2rem;
background: lightskyblue;
}
概括
太棒了!我们编写了第一个自定义 Hook。你也可以看看一些常用的自定义 Hook 库(例如react-use或rooks),尝试复现其中一个 Hook 来练习。
文章来源:https://dev.to/rajatkapoor/writing-my-first-custom-react-hook-useoutsideclick-46gg

