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

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

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

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]删除
最新教程 更多>
  • 如何使用组在MySQL中旋转数据?
    如何使用组在MySQL中旋转数据?
    在关系数据库中使用mySQL组使用mySQL组进行查询结果,在关系数据库中使用MySQL组,转移数据的数据是指重新排列的行和列的重排以增强数据可视化。在这里,我们面对一个共同的挑战:使用组的组将数据从基于行的基于列的转换为基于列。 Let's consider the following ...
    编程 发布于2025-05-07
  • 如何解决AppEngine中“无法猜测文件类型,使用application/octet-stream...”错误?
    如何解决AppEngine中“无法猜测文件类型,使用application/octet-stream...”错误?
    appEngine静态文件mime type override ,静态文件处理程序有时可以覆盖正确的mime类型,在错误消息中导致错误消息:“无法猜测mimeType for for file for file for [File]。 application/application/octet...
    编程 发布于2025-05-07
  • 反射动态实现Go接口用于RPC方法探索
    反射动态实现Go接口用于RPC方法探索
    在GO 使用反射来实现定义RPC式方法的界面。例如,考虑一个接口,例如:键入myService接口{ 登录(用户名,密码字符串)(sessionId int,错误错误) helloworld(sessionid int)(hi String,错误错误) } 替代方案而不是依靠反射...
    编程 发布于2025-05-07
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-05-07
  • PHP SimpleXML解析带命名空间冒号的XML方法
    PHP SimpleXML解析带命名空间冒号的XML方法
    在php 很少,请使用该限制很大,很少有很高。例如:这种技术可确保可以通过遍历XML树和使用儿童()方法()方法的XML树和切换名称空间来访问名称空间内的元素。
    编程 发布于2025-05-07
  • 哪种在JavaScript中声明多个变量的方法更可维护?
    哪种在JavaScript中声明多个变量的方法更可维护?
    在JavaScript中声明多个变量:探索两个方法在JavaScript中,开发人员经常遇到需要声明多个变量的需要。对此的两种常见方法是:在单独的行上声明每个变量: 当涉及性能时,这两种方法本质上都是等效的。但是,可维护性可能会有所不同。 第一个方法被认为更易于维护。每个声明都是其自己的语句,使其...
    编程 发布于2025-05-07
  • 在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在程序退出之前,我需要在C ++中明确删除堆的堆分配吗?
    在C中的显式删除 在C中的动态内存分配时,开发人员通常会想知道是否有必要在heap-procal extrable exit exit上进行手动调用“ delete”操作员,但开发人员通常会想知道是否需要手动调用“ delete”操作员。本文深入研究了这个主题。 在C主函数中,使用了动态分配变量(H...
    编程 发布于2025-05-07
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-05-07
  • 如何高效地在一个事务中插入数据到多个MySQL表?
    如何高效地在一个事务中插入数据到多个MySQL表?
    mySQL插入到多个表中,该数据可能会产生意外的结果。虽然似乎有多个查询可以解决问题,但将从用户表的自动信息ID与配置文件表的手动用户ID相关联提出了挑战。使用Transactions和last_insert_id() 插入用户(用户名,密码)值('test','test...
    编程 发布于2025-05-07
  • eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    称量()和ast.literal_eval()中的Python Security 在使用用户输入时,必须优先确保安全性。强大的Python功能Eval()通常是作为潜在解决方案而出现的,但担心其潜在风险。 This article delves into the differences betwee...
    编程 发布于2025-05-07
  • 如何在GO编译器中自定义编译优化?
    如何在GO编译器中自定义编译优化?
    在GO编译器中自定义编译优化 GO中的默认编译过程遵循特定的优化策略。 However, users may need to adjust these optimizations for specific requirements.Optimization Control in Go Compi...
    编程 发布于2025-05-07
  • 如何干净地删除匿名JavaScript事件处理程序?
    如何干净地删除匿名JavaScript事件处理程序?
    删除匿名事件侦听器将匿名事件侦听器添加到元素中会提供灵活性和简单性,但是当要删除它们时,可以构成挑战,而无需替换元素本身就可以替换一个问题。 element? element.addeventlistener(event,function(){/在这里工作/},false); 要解决此问题,请考虑...
    编程 发布于2025-05-07
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义函数。 runkit_function_renction_re...
    编程 发布于2025-05-07
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-05-07
  • 如何使用Python有效地以相反顺序读取大型文件?
    如何使用Python有效地以相反顺序读取大型文件?
    在python 反向行读取器生成器 == ord('\ n'): 缓冲区=缓冲区[:-1] 剩余_size- = buf_size lines = buffer.split('\ n'....
    编程 发布于2025-05-07

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

Copyright© 2022 湘ICP备2022001581号-3