发布于 2026-01-06 3 阅读
0

编写我的第一个自定义 React Hook——useOutsideClick 让我们开始吧

编写我的第一个自定义 React Hook——useOutsideClick

让我们开始吧

React Hooks 的推出彻底改变了 React 生态系统。我使用 React Hooks 已经有一段时间了,而且非常喜欢它。但和很多其他开发者一样,我从未编写过自定义的 React Hook。这主要是因为:首先,我需要的所有功能都可以在第三方 Hooks 库中找到;其次,我比较拖延。

我坚信实践出真知。因此,我将创建一个非常简单的钩子函数——useOutsideClick。这个钩子函数可以帮助我们在用户点击组件外部时触发一个函数。

我们可以在哪里使用它?

  1. 当用户点击组件外部时,关闭组件的展开状态。
  2. 当用户点击模态框外部时,关闭模态框。

以及更多

我们将如何实现这一点?

这可能不是最佳方法,但我在之前的基于类的组件中一直使用一种非常简单的方法。我将尝试用自定义钩子来复现这种方法。具体步骤如下:

  1. 我们将onClickListenerdocument组件安装时添加一个。
  2. 在这个点击监听器中,outsideClickHandler当点击目标位于所需组件之外时,我们将触发该监听器。

让我们开始吧

您可以在此GitHub 仓库中找到本教程的最终代码,并在此处找到一个可运行的在线演示。

让我们创建一个 React 应用,并使用以下命令运行它。

npx create-react-app useOutsideClick
npm install # to install all dependencies
npm run start # to run the app
Enter fullscreen mode Exit fullscreen mode

我们首先会在一个简单的函数组件中创建外部点击功能,然后尝试将其提取到一个自定义钩子中。

让我们编辑src/App.js成这样:

import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <div className="main">Click me</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

并更新样式./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;
}
Enter fullscreen mode Exit fullscreen mode

如果你查看浏览器,你会看到类似这样的内容:

frame_safari_dark.png

添加外部点击功能

现在我们将尝试使用useEffectuseRef钩子来检测用户何时点击了显示“点击我”的 div 元素之外的区域

我们将首先创建一个新的ref外部组件<div>,用于检测点击事件。

const mainRef = useRef();
Enter fullscreen mode Exit fullscreen mode

并将其作为refprop 传递给 div 元素。

<div className="main" ref={mainRef}>
Enter fullscreen mode Exit fullscreen mode

在点击事件处理程序中,我们将检查点击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");
    }
  };
Enter fullscreen mode Exit fullscreen mode

我们希望在组件挂载时或引用发生变化时,监听整个文档上的点击事件。我们将使用useEffect钩子来实现这一点。

useEffect(() => {
    document.addEventListener("click", onOutsideClick);
    // cleaning up the event listener when the component unmounts
    return () => {
      document.removeEventListener("click", onOutsideClick);
    };
  }, [mainRef]);
Enter fullscreen mode Exit fullscreen mode

我们的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>
  );
}
Enter fullscreen mode Exit fullscreen mode

就这样。现在我们只需要将这个功能提取到一个自定义钩子中即可。

创建自定义钩子

创建一个名为 . 的新文件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]);
};
Enter fullscreen mode Exit fullscreen mode

现在我们将在应用程序中使用它。

#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>
  );
}
Enter fullscreen mode Exit fullscreen mode

一切都完美运行🎉

例子

现在我们将更新应用程序以展示其中一个用例。当用户点击蓝色按钮时<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>
  );
}

Enter fullscreen mode Exit fullscreen mode
/* src/styles.css */

/* add this */
.more {
  text-align: center;
  font-size: 1.2rem;
  background: lightskyblue;
}
Enter fullscreen mode Exit fullscreen mode

现在的情况就是这样。
CPT2105092244-1680x939.gif

概括

太棒了!我们编写了第一个自定义 Hook。你也可以看看一些常用的自定义 Hook 库(例如react-userooks),尝试复现其中一个 Hook 来练习。

文章来源:https://dev.to/rajatkapoor/writing-my-first-custom-react-hook-useoutsideclick-46gg