”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > React 中的智能下拉菜单:使用 useReducer 和 useRef 进行外部点击处理

React 中的智能下拉菜单:使用 useReducer 和 useRef 进行外部点击处理

发布于2024-11-03
浏览:290

Smart Dropdowns in React: Using useReducer and useRef for Outside Click Handling

如何使用 useReducer 和 useRef 在 React 中使用 Tailwind CSS 创建下拉菜单

在 React 应用程序中创建下拉菜单可以通过提供一种紧凑的方式来导航和访问附加信息,从而增强用户体验。在本指南中,我们将实现两个下拉菜单,一个用于通知,一个用于用户配置文件设置,使用 useReducer 进行状态管理,使用 useRef 处理外部点击以关闭下拉菜单。我们还将合并 Font Awesome 图标,以获得精美的外观。

介绍

在现代 Web 开发中,有效管理用户界面至关重要。 React 与 Tailwind CSS 相结合,提供了用于构建响应式组件的强大工具包。我们将学习如何在 React 中处理下拉菜单功能,确保单击下拉菜单外部将关闭它,同时保持独立打开或关闭每个下拉菜单的能力。

useReducer 和 useRef 是什么?

在深入代码之前,让我们先了解一下我们将使用的两个 React hook:

  • useReducer:此钩子是 useState 的替代方案,用于管理功能组件中的状态。它对于管理复杂的状态逻辑和多个状态变量特别有用。 useReducer 钩子接受一个减速器函数和一个初始状态,返回当前状态和一个调度函数来更新该状态。

  • useRef:这个钩子提供了一种直接引用 DOM 元素的方法。它对于访问和操作元素而不触发重新渲染非常有用。在我们的例子中,我们将使用 useRef 来检查点击是否发生在下拉菜单之外。

逐步实施

第 1 步:设置项目

首先,确保您有一个使用 Tailwind CSS 设置的 React 项目。如果没有,可以使用 Create React App 创建它:

npx create-react-app my-dropdown-app
cd my-dropdown-app
npm install tailwindcss
npx tailwindcss init

通过将以下行添加到 tailwind.config.js 来配置 Tailwind:

module.exports = {
  purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
  darkMode: false,
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
};

然后,将 Tailwind 指令添加到您的 index.css 中:

@tailwind base;
@tailwind components;
@tailwind utilities;

第 2 步:安装 Font Awesome

要使用Font Awesome图标,您需要安装@fortawesome/react-fontawesome包:

npm install @fortawesome/react-fontawesome @fortawesome/free-solid-svg-icons

第三步:创建导航栏组件

在 src 目录中,创建一个名为 Navbar.tsx 的新文件。该组件将包含下拉菜单。

实施导航栏代码

这是 Navbar 组件的代码,它利用 useReducer 和 useRef 来处理下拉状态和外部点击。

import React, { useRef, useEffect, useReducer } from "react";
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBell, faUser, faCaretDown } from '@fortawesome/free-solid-svg-icons';

interface IState {
  isProfileOpen: boolean;
  isNotificationOpen: boolean;
}

type Action =
  | { type: 'toggleProfile' }
  | { type: 'toggleNotification' }
  | { type: 'closeAll' };

function reducer(state: IState, action: Action): IState {
  switch (action.type) {
    case 'toggleProfile':
      return {
        isProfileOpen: !state.isProfileOpen,
        isNotificationOpen: false
      };
    case 'toggleNotification':
      return {
        isProfileOpen: false,
        isNotificationOpen: !state.isNotificationOpen
      };
    case 'closeAll':
      return {
        isProfileOpen: false,
        isNotificationOpen: false
      };
    default:
      return state;
  }
}

const Navbar: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, { isProfileOpen: false, isNotificationOpen: false });
  const profileDropdownRef = useRef(null);
  const notificationDropdownRef = useRef(null);

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as Node;

      if (
        (profileDropdownRef.current && !profileDropdownRef.current.contains(target)) ||
        (notificationDropdownRef.current && !notificationDropdownRef.current.contains(target))
      ) {
        dispatch({ type: 'closeAll' });
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, []);

  const toggleProfileDropdown = (event: React.MouseEvent) => {
    event.stopPropagation();
    dispatch({ type: 'toggleProfile' });
  };

  const toggleNotificationDropdown = (event: React.MouseEvent) => {
    event.stopPropagation();
    dispatch({ type: 'toggleNotification' });
  };

  const closeDropdowns = () => {
    dispatch({ type: 'closeAll' });
  };

  const notificationItems = [
    { text: "New data received", time: "2 Days Ago" },
    { text: "New update available", time: "1 Day Ago" },
    { text: "Scheduled maintenance", time: "3 Days Ago" },
  ];

  const profileItems = [
    { label: "Profile", link: "#" },
    { label: "Settings", link: "#" },
    { label: "Logout", link: "#" }
  ];

  return (
    
); }; export default Navbar;

第 4 步:将导航栏集成到您的应用程序中

打开您的 App.tsx 文件并导入 Navbar 组件以将其包含在您的应用程序布局中。

import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Navbar from './components/Navbar';

const App: React.FC = () => {
  return (
    
      
      

Welcome to DC Dashboard!

{/* Other components and content */}
); }; export default App;

第 5 步:使用 Tailwind CSS 进行样式设置

Tailwind CSS 提供的类应该已经给出了简洁的设计。但是,请随意根据需要自定义样式。

第 6 步:测试应用程序

通过运行以下命令启动您的应用程序:

bash
npm start

您现在应该在应用程序顶部看到一个导航栏,其中包含通知和用户配置文件设置的功能下拉菜单。

常问问题

1。本例中 useReducer 钩子如何工作?

useReducer 钩子允许我们有效地管理多个下拉菜单的状态。我们定义一个reducer函数,它接受当前状态和返回新状态的操作。此模式有助于切换下拉菜单和处理立即关闭所有下拉菜单的逻辑。

2.为什么使用useRef?

我们使用 useRef 来引用下拉元素。这让我们可以检查点击事件是否发生在这些元素之外。如果是这样,我们会发送一个操作来关闭下拉菜单,以确保干净的用户体验。

3.我可以添加更多下拉菜单吗?

是的!您可以扩展减速器中的状态并类似地添加更多下拉菜单。只需确保每个下拉菜单都有自己的引用和切换功能。

4。此实现是否需要 Tailwind CSS?

不,Tailwind CSS 不是强制性的。您可以使用任何 CSS 框架或自定义 CSS 样式来设置下拉菜单的样式,但 Tailwind 使样式设置更快、响应更灵敏。

结论

在本指南中,您学习了如何使用 useReducer 进行状态管理和 useRef 处理外部点击,在 React 中创建功能性下拉菜单。这种方法提供了一种干净高效的方式来管理复杂的 UI 交互,从而增强整体用户体验。您可以在此基础上随意构建并自定义它以满足您的应用程序的需求!

版本声明 本文转载于:https://dev.to/chintanonweb/smart-dropdowns-in-react-using-usereducer-and-useref-for-outside-click-handling-138h?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    Red: How to Redirect Multiple User Types to Respective ActivitiesUnderstanding the ProblemIn a Firebase-based voting app with three distinct user type...
    编程 发布于2025-07-18
  • PHP与C++函数重载处理的区别
    PHP与C++函数重载处理的区别
    作为经验丰富的C开发人员脱离谜题,您可能会遇到功能超载的概念。这个概念虽然在C中普遍,但在PHP中构成了独特的挑战。让我们深入研究PHP功能过载的复杂性,并探索其提供的可能性。在PHP中理解php的方法在PHP中,函数超载的概念(如C等语言)不存在。函数签名仅由其名称定义,而与他们的参数列表无关。...
    编程 发布于2025-07-18
  • Java为何无法创建泛型数组?
    Java为何无法创建泛型数组?
    通用阵列创建错误 arrayList [2]; JAVA报告了“通用数组创建”错误。为什么不允许这样做?答案:Create an Auxiliary Class:public static ArrayList<myObject>[] a = new ArrayList<myO...
    编程 发布于2025-07-18
  • 如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    为有效的slug生成首先,该函数用指定的分隔符替换所有非字母或数字字符。此步骤可确保slug遵守URL惯例。随后,它采用ICONV函数将文本简化为us-ascii兼容格式,从而允许更广泛的字符集合兼容性。接下来,该函数使用正则表达式删除了不需要的字符,例如特殊字符和空格。此步骤可确保slug仅包含...
    编程 发布于2025-07-18
  • 如何使用Regex在PHP中有效地提取括号内的文本
    如何使用Regex在PHP中有效地提取括号内的文本
    php:在括号内提取文本在处理括号内的文本时,找到最有效的解决方案是必不可少的。一种方法是利用PHP的字符串操作函数,如下所示: 作为替代 $ text ='忽略除此之外的一切(text)'; preg_match('#((。 &&& [Regex使用模式来搜索特...
    编程 发布于2025-07-18
  • 图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    在chrome 中删除一个频繁的问题时,在与Chrome and IE9中的图像一起工作时,遇到了一个频繁的问题。和“边境:无;”在CSS中。要解决此问题,请考虑以下方法: Chrome具有忽略“ border:none; none;”的已知错误,风格。要解决此问题,请使用以下CSS ID块创建带...
    编程 发布于2025-07-18
  • 在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8表中将latin1字符转换为utf8 ,您遇到了一个问题,其中含义的字符(例如,“jáuòiñe”)在utf8 table tabled tablesset中被extect(例如,“致电。为了解决此问题,您正在尝试使用“ mb_convert_encoding”和“ iconv”转换受...
    编程 发布于2025-07-18
  • 如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    如何将PANDAS DataFrame列转换为DateTime格式并按日期过滤?
    Transform Pandas DataFrame Column to DateTime FormatScenario:Data within a Pandas DataFrame often exists in various formats, including strings.使用时间数据时...
    编程 发布于2025-07-18
  • 查找当前执行JavaScript的脚本元素方法
    查找当前执行JavaScript的脚本元素方法
    如何引用当前执行脚本的脚本元素在某些方案中理解问题在某些方案中,开发人员可能需要将其他脚本动态加载其他脚本。但是,如果Head Element尚未完全渲染,则使用document.getElementsbytagname('head')[0] .appendChild(v)的常规方...
    编程 发布于2025-07-18
  • 哪种方法更有效地用于点 - 填点检测:射线跟踪或matplotlib \的路径contains_points?
    哪种方法更有效地用于点 - 填点检测:射线跟踪或matplotlib \的路径contains_points?
    在Python Matplotlib's path.contains_points FunctionMatplotlib's path.contains_points function employs a path object to represent the polygon.它...
    编程 发布于2025-07-18
  • 如何在无序集合中为元组实现通用哈希功能?
    如何在无序集合中为元组实现通用哈希功能?
    在未订购的集合中的元素要纠正此问题,一种方法是手动为特定元组类型定义哈希函数,例如: template template template 。 struct std :: hash { size_t operator()(std :: tuple const&tuple)const {...
    编程 发布于2025-07-18
  • Java的Map.Entry和SimpleEntry如何简化键值对管理?
    Java的Map.Entry和SimpleEntry如何简化键值对管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    编程 发布于2025-07-18
  • 如何高效地在一个事务中插入数据到多个MySQL表?
    如何高效地在一个事务中插入数据到多个MySQL表?
    mySQL插入到多个表中,该数据可能会产生意外的结果。虽然似乎有多个查询可以解决问题,但将从用户表的自动信息ID与配置文件表的手动用户ID相关联提出了挑战。使用Transactions和last_insert_id() 插入用户(用户名,密码)值('test','test...
    编程 发布于2025-07-18
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中3个Party Package将另一个PAXPANCE带有导入式套件之间的另一个软件包,并在导入式套件之间导入另一个软件包。如回声消息所证明的那样: go.etcd.io/bbolt [&&&&&&&&&&&&&&&&...
    编程 发布于2025-07-18
  • 在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在C中的显式删除 在C中的动态内存分配时,开发人员通常会想知道是否有必要在heap-procal extrable exit exit上进行手动调用“ delete”操作员,但开发人员通常会想知道是否需要手动调用“ delete”操作员。本文深入研究了这个主题。 在C主函数中,使用了动态分配变量(H...
    编程 发布于2025-07-18

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3