React+TS前台项目实战(十四)-- 响应式头部导航+切换语言相关组件封装

news2024/7/6 18:07:22

文章目录

  • 前言
  • Header头部相关组件
    • 1. 功能分析
    • 2. 相关组件代码+详细注释
    • 3. 使用方式
    • 4. Gif图效果展示
  • 总结


前言

在这篇博客中,我们将封装一个头部组件,根据不同设备类型来显示不同的导航菜单,会继续使用 React hooks 和styled-components库来构建这个组件,此外,也会实现切换国际化功能。


Header头部相关组件

1. 功能分析

(1)根据用户的设备类型(移动设备或PC设备),动态渲染不同的导航菜单。
(2)封装的 useIsMobile hook函数,判断用户的设备类型
(3)封装导航菜单 NavMenu组件,根据是否是移动设备来决定渲染哪个导航菜单
(4)封装国际化语言切换弹窗组件,实现切换语言功能
(5)移动端导航菜单按钮由三个div元素组成,点击后元素添加动画效果,并控制导航菜单显示与否
(5)使用到的全局组件请看之前文章国际化配置、全局常用组件弹窗Dialog封装、全局常用组件Select封装、全局常用组件Link封装

2. 相关组件代码+详细注释

(1)首先,先来封装一个导航菜单组件

// @/components/Header/NavMenu/index.tsx
import { memo, FC } from "react";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
import Link from "@/components/Link";
import LanguagePanel from "@/components/Header/LanguagePanel";
import { MobileMenuList, PCMenuList } from "./styled";

interface navListMap {
  name: string; // 菜单名称
  url: string; // 菜单链接地址
}

/**
 * 获取导航菜单列表
 * @returns {navListMap[]} 导航菜单列表
 */
const useNavList = () => {
  const { t } = useTranslation();
  const list: navListMap[] = [
    {
      name: t("navbar.home"),
      url: "/home",
    },
    {
      name: t("navbar.nervos_dao"),
      url: "/nervosdao",
    },
    {
      name: t("navbar.tokens"),
      url: "/tokens",
    },
    {
      name: t("navbar.fee_rate"),
      url: "/fee-rate-tracker",
    },
    {
      name: t("navbar.charts"),
      url: "/charts",
    },
  ];
  return list;
};

/**
 * 移动端导航菜单
 * @returns {JSX.Element}
 */
const MobileMenu: FC<{ navList: navListMap[] }> = ({ navList }) => {
  return (
    <MobileMenuList>
      {navList.map((item) => (
        <Link className={classNames("mobile-menu-list")} to={item.url ?? "/"} key={item.name}>
          {item.name}
        </Link>
      ))}
      <LanguagePanel /> {/* 语言选择组件 */}
    </MobileMenuList>
  );
};
/**
 * 桌面端导航菜单
 * @returns {JSX.Element}
 */
const PCMenu: FC<{ navList: navListMap[] }> = ({ navList }) => {
  return (
    <>
      <PCMenuList>
        {navList.map((item) => (
          <Link className={classNames("nav-item")} to={item.url ?? "/"} key={item.name}>
            {item.name}
          </Link>
        ))}
      </PCMenuList>
      <LanguagePanel /> {/* 语言选择组件 */}
    </>
  );
};

/**
 * 导航菜单组件
 * @param {boolean} isMobile - 是否是移动端
 * @returns {JSX.Element} 导航菜单组件
 */
export default memo<{ isMobile: boolean }>(({ isMobile }) => {
  const navList = useNavList();
  return isMobile ? <MobileMenu navList={navList} /> : <PCMenu navList={navList}></PCMenu>;
});
-----------------------------------------------------------------------------
// @/components/Header/NavMenu/styled.tsx
import styled from "styled-components";
export const MobileMenuList = styled.div`
  width: 100vw;
  height: calc(100vh - var(--cd-navbar-height));
  position: absolute;
  top: var(--cd-navbar-height);
  box-sizing: border-box;
  left: 0;
  background: #2b2c30;
  .mobile-menu-list {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin: 20px 40px;
    color: #fff;
  }
  .language-switch {
    margin-left: 40px;
    text-align: left;
  }
`;
export const PCMenuList = styled.div`
  display: flex;
  flex: 1;
  min-width: 0;
  .nav-item {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    margin-right: 50px;
    color: white;
    &:hover {
      color: var(--cd-primary-color);
    }
  }
`;

(2)接下来我们开始封装国际化语言切换组件,在其中会引用到之前文章封装的Dialog组件和Select组件

// @/components/Header/LanguagePanel/index.tsx
import { useState, memo } from "react";
import { useLocation } from "react-router";
import { useTranslation } from "react-i18next";
import { SupportedLngs, useChangeLanguage } from "@/config/i18n";
import { LanguageContainer } from "./styled";
import Dialog from "@/pages/components/commonDialog";
import Select from "@/components/Select";
type Option = {
  label: string; // 选项的显示文本
  value: string; // 选项的值
};
export default memo(() => {
  // 获取当前语言
  const { pathname } = useLocation();
  const currentLanguage = pathname.split("/")[1];
  // 获取语言切换的钩子函数
  const { changeLanguage } = useChangeLanguage();
  // 获取国际化的钩子函数
  const { t } = useTranslation();
  // 控制语言弹框的显示隐藏
  const [languageModalVisible, setLanguageModalVisible] = useState(false);
  // 当前选中的语言
  const [language, setLanguage] = useState(currentLanguage);
  // 获取所有支持的语言
  const lngOptions: Option[] = SupportedLngs.map((lng) => ({
    value: lng,
    label: t(`navbar.language_${lng}`),
  }));
  // 关闭切换语言弹框
  const handlerClose = () => {
    setLanguageModalVisible(!languageModalVisible);
  };
  // 确定切换语言
  const handlerDone = () => {
    return new Promise((resolve) => {
      changeLanguage(language);
      handlerClose();
      resolve(true);
    });
  };
  // 切换语言
  const handlerLanguageChange = (value: string) => {
    setLanguage(value);
  };
  // 打开语言弹框
  const handlerOpenLanguage = () => {
    setLanguageModalVisible(!languageModalVisible);
  };
  // 语言选择弹框
  return (
    <>
      {/* 语言切换 */}
      <LanguageContainer  className={classNames("language-switch")} onClick={handlerOpenLanguage}>
        <i className="iconfont icon-guojihua"></i>
        <span>{t("navbar.language")}</span>
      </LanguageContainer>
      {/* 语言选择弹框 */}
      <Dialog title={t("navbar.language_switch")} doneText={t("button.confirm")} show={languageModalVisible} onClose={handlerClose} onDoneClick={handlerDone}>
        <Select options={lngOptions} onChange={handlerLanguageChange} defaultValue={currentLanguage} placeholder={t("placeholder.default")}></Select>
      </Dialog>
    </>
  );
});
-----------------------------------------------------------------------------
// @/components/Header/LanguagePanel/styled.tsx
import styled from "styled-components";
export const LanguageContainer = styled.div`
  color: #ffffff;
  cursor: pointer;
  span {
    margin-left: 5px;
  }
`;

(3)最后一步,封装父组件Header组件,并引入NavMenu组件和LanguagePanel组件

// @/components/Header/index.tsx
import { FC, useState } from "react";
import classNames from "classnames";
import LogoIcon from "@/assets/headerLogo.png";
import { Header, Logo, MobileMenuContainer, HeaderContainer } from "./styled";
import { useIsMobile } from "@/hooks";
import NavMenu from "./NavMenu";

// 头部组件
const HeaderComponent: FC = () => {
  // 判断是否是移动端
  const isMobile = useIsMobile();

  // PC端导航菜单组件
  const PCMenus: FC = () => {
    return <NavMenu isMobile={isMobile} />;
  };

  // 移动端导航菜单
  const MobileMenus: FC = () => {
    // 控制移动端菜单是否显示的状态
    const [mobileMenuVisible, setMobileMenuVisible] = useState<boolean>(false);

    return (
      <MobileMenuContainer>
        <div className={mobileMenuVisible ? "close" : ""} onClick={() => setMobileMenuVisible(!mobileMenuVisible)}>
          <div className={classNames("firstLine")} />
          <div className={classNames("secondLine")} />
          <div className={classNames("thirdLine")} />
        </div>
        {mobileMenuVisible && isMobile && <NavMenu isMobile={isMobile} />}
      </MobileMenuContainer>
    );
  };

  return (
    <HeaderContainer>
      <Header>
        <Logo to="/">
          <img src={LogoIcon} alt="logo" />
        </Logo>
        {isMobile ? <MobileMenus /> : <PCMenus />}
      </Header>
    </HeaderContainer>
  );
};

export default HeaderComponent;
------------------------------------------------------------------------------
// @/components/Header/styled.tsx
import styled from "styled-components";
import Link from "../Link";
export const HeaderContainer = styled.div`
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
`;
export const Header = styled.div`
  width: 100%;
  min-height: var(--cd-navbar-height);
  background-color: #2b2c30;
  overflow: visible;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  padding: 0 120px;

  @media (max-width: 1440px) {
    padding: 0 100px;
  }

  @media (max-width: 1200px) {
    padding: 0 45px;
  }

  @media (max-width: 780px) {
    padding: 0 18px;
  }
`;

export const Logo = styled(Link)`
  display: flex;
  align-items: center;
  margin-right: 40px;

  img {
    width: 140px;
  }
`;

export const MobileMenuContainer = styled.div`
  display: flex;
  justify-content: flex-end;
  flex: 1;
  .firstLine,
  .secondLine,
  .thirdLine {
    width: 18px;
    height: 2px;
    background-color: #fff;
    margin: 5px 0;
    transition: 0.4s;
  }
  .close {
    .firstLine {
      transform: rotate(45deg) translate(6px, 3px);
    }
    .secondLine {
      opacity: 0;
    }
    .thirdLine {
      transform: rotate(-45deg) translate(6px, -4px);
    }
  }
  .mobile-menu {
    width: 100vw;
    height: calc(100vh - var(--cd-navbar-height));
    position: absolute;
    top: var(--cd-navbar-height);
    box-sizing: border-box;
    left: 0;
    background: #2b2c30;
    .mobile-menu-list {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      margin: 20px 40px;
      color: #fff;
    }
  }
`;

`;

(4)贴上封装的判断设备的钩子函数,自行取用即可

import { useEffect, useState } from "react";
import variables from "@/styles/variables.module.scss";

/**
 * copied from https://usehooks-ts.com/react-hook/use-media-query
 */
export function useMediaQuery(query: string): boolean {
  const getMatches = (query: string): boolean => {
    // Prevents SSR issues
    if (typeof window !== "undefined") {
      return window.matchMedia(query).matches;
    }
    return false;
  };

  const [matches, setMatches] = useState<boolean>(getMatches(query));

  useEffect(() => {
    const matchMedia = window.matchMedia(query);
    const handleChange = () => setMatches(getMatches(query));

    // Triggered at the first client-side load and if query changes
    handleChange();

    // Listen matchMedia
    if (matchMedia.addListener) {
      matchMedia.addListener(handleChange);
    } else {
      matchMedia.addEventListener("change", handleChange);
    }

    return () => {
      if (matchMedia.removeListener) {
        matchMedia.removeListener(handleChange);
      } else {
        matchMedia.removeEventListener("change", handleChange);
      }
    };
  }, [query]);

  return matches;
}

/**
 * 移动端断点,单位为px
 */
export const mobileBreakPoint = Number(variables.mobileBreakPoint.replace("px", ""));

/**
 * 是否是大型屏幕
 */
export const useIsXXLBreakPoint = () => useMediaQuery(`(max-width: ${variables.xxlBreakPoint})`);

/**
 * 是否处是移动端
 */
export const useIsMobile = () => useMediaQuery(`(max-width: ${variables.mobileBreakPoint})`);

/**
 * 是否处于最大宽度为extraLargeBreakPoint的断点,如果exact为true,则需要同时不处于mobileBreakPoint的断点
 */
export const useIsExtraLarge = (exact = false) => {
  const isMobile = useIsMobile();
  const isExtraLarge = useMediaQuery(`(max-width: ${variables.extraLargeBreakPoint})`);
  return !exact ? isExtraLarge : isExtraLarge && !isMobile;
};

3. 使用方式

// 在layout布局组件中引入
import Header from "@/components/Header";
// 使用
<Header />

4. Gif图效果展示

在这里插入图片描述
在这里插入图片描述


总结

下一篇讲【开始首页编码教学】。关注本栏目,将实时更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1860310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

裸机写代码(Windows.Linux环境搭建)

目录 1.工具/原料 2.配置环境变量 2.1开发环境Windows搭建 2.1.1概述 2.1.1.1. 系统环境变量 2.1.1.2. 用户环境变量 2.1.1.3.根据你的实际情况选择配置用户变量还是系统变量&#xff0c; 2.1.1.4.环境变量各个变量名的作用 2.1.1.5.具体配置实例&#xff1a; 2.1.1.6…

[深度学习]循环神经网络RNN

RNN&#xff08;Recurrent Neural Network&#xff0c;即循环神经网络&#xff09;是一类用于处理序列数据的神经网络&#xff0c;广泛应用于自然语言处理&#xff08;NLP&#xff09;、时间序列预测、语音识别等领域。与传统的前馈神经网络不同&#xff0c;RNN具有循环结构&am…

【银河麒麟】云平台查看内存占用与实际内存占用不一致,分析处理过程,附代码

1.需求/问题描述 发现云平台查看内存占用与实际内存占用不一致。 2.分析过程 在系统中获取虚拟机内存使用率目前主要有两种方式&#xff0c;一种是通过virsh dommemstat获取&#xff0c;另外一种是通过qga接口获取。由于之前修复界面虚拟机cpu使用率时为qga接口获取&#xff…

安装VEX外部编辑器

Houdini20配置VEX外部编辑器方法_哔哩哔哩_bilibili 下载并安装Visual Studio Code软件&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 在Visual Studio Code软件内&#xff0c;安装相关插件&#xff0c;如&#xff1a; 中文汉化插件vex插件 安装Houdini Expr…

八、yolov8模型预测和模型导出(目标检测)

模型查看 模型预测 模型导出 模型训练完成后&#xff0c;找到训练文件生成文件夹&#xff0c;里面包含wights、过程图、曲线图。 模型预测 1、在以下文件夹中放入需要预测的图&#xff1b; 2、找到detect文件下的predict.py文件&#xff0c;修改以下内容。 3、右键点击…

AI降重技术:论文查重率的智能解决方案

现在大部分学校已经进入到论文查重降重的阶段了。如果查重率居高不下&#xff0c;延毕的威胁可能就在眼前。对于即将告别校园的学子们&#xff0c;这无疑是个噩梦。四年磨一剑&#xff0c;谁也不想在最后关头功亏一篑。 查重率过高&#xff0c;无非以下两种原因。要么是作为“…

【编译原理】语法制导翻译

1.导入 语法制导翻译是处理语义的基本方法&#xff0c;它以语法分析为 基础&#xff0c;在语法分析得到语言结构的结果时&#xff0c;对附着于此结构 的语义进行处理&#xff0c;如计算表达式的值、生成中间代码等 2.语法与语义 语法与语义的关系 语法是指语言的结构、即语言的…

html5+css简易实现图书网联系我们页面

html5css简易实现图书网联系我们页面 完整代码已资源绑定

PD虚拟机支持M3吗 PD虚拟机怎样配置图形卡

最近有很多人在问M3芯片的苹果电脑和M2相比&#xff0c;有哪些提升的功能。实际上&#xff0c;M3芯片的苹果电脑拥有与M2相同的CPU与GPU数量&#xff0c;但比M2多50亿个晶体管&#xff0c;并引入了动态缓存、增强型神经网络引擎等技术&#xff0c;性能、功能均进一步加强。面对…

【motan rpc 懒加载】异常

文章目录 升级版本解决问题我使用的有问题的版本配置懒加载错误的版本配置了懒加载 但是不生效 lazyInit"true" 启动不是懒加载 会报错一次官方回复 升级版本解决问题 <version.motan>1.2.1</version.motan><dependency><groupId>com.weibo…

Kotlin设计模式:享元模式(Flyweight Pattern)

Kotlin设计模式&#xff1a;享元模式&#xff08;Flyweight Pattern&#xff09; 在移动应用开发中&#xff0c;内存和CPU资源是非常宝贵的。享元模式&#xff08;Flyweight Pattern&#xff09;是一种设计模式&#xff0c;旨在通过对象重用来优化内存使用和性能。本文将深入探…

LabVIEW程序闪退问题

LabVIEW程序出现闪退问题可能源于多个方面&#xff0c;包括软件兼容性、内存管理、代码质量、硬件兼容性和环境因素。本文将从这些角度进行详细分析&#xff0c;探讨可能的原因和解决方案&#xff0c;并提供预防措施&#xff0c;以帮助用户避免和解决LabVIEW程序闪退的问题。 1…

STM32学习-HAL库 串口通信

学完标准库之后&#xff0c;本来想学习freertos的&#xff0c;但是看了很多教程都是移植的HAL库程序&#xff0c;这里再学习一些HAL库的内容&#xff0c;有了基础这里直接学习主要的外设。 HAL库对于串口主要有两个结构体UART_InitTypeDef和UART_HandleTypeDef&#xff0c;前者…

【CT】LeetCode手撕—56. 合并区间

目录 题目1- 思路2- 实现⭐56. 合并区间——题解思路 3- ACM 实现 题目 原题连接&#xff1a;56. 合并区间 1- 思路 模式识别&#xff1a;合并区间 ——> 数组先排序 思路 1.先对数组内容进行排序 ——> 定义 left、right 根据排序后的结果&#xff0c;更新 right2.遍…

Spring Boot整合Druid:轻松实现SQL监控和数据库密码加密

文章目录 1 引言1.1 简介1.2 Druid的功能1.3 竞品对比 2 准备工作2.1 项目环境 3 集成Druid3.1 添加依赖3.2 配置Druid3.3 编写测试类测试3.4 访问控制台3.5 测试SQL监控3.6 数据库密码加密3.6.1 执行命令加密数据库密码3.6.2 配置参数3.6.3 测试 4 总结 1 引言 1.1 简介 Dru…

如何处理消息积压问题

什么是MQ消息积压&#xff1f; MQ消息积压是指消息队列中的消息无法及时处理和消费&#xff0c;导致队列中消息累积过多的情况。 消息积压后果&#xff1a; ①&#xff1a;消息不能及时消费&#xff0c;导致任务不能及时处理 ②&#xff1a;下游消费者处理大量的消息任务&#…

品牌为什么需要3D营销?

在对比传统品牌营销手段时&#xff0c;线上3D互动营销以其更为生动的展示效果脱颖而出。它通过构建虚拟仿真场景&#xff0c;创造出一个身临其境的三维空间&#xff0c;充分满足了客户对实体质感空间的期待。不仅如此&#xff0c;线上3D互动营销还能实现全天候24小时无间断服务…

计量中的标准物是什么?仪器校准机构如何管理标准物?

计量标准中&#xff0c;标准物是常常使用的一种计量消耗品。为什么说是“消耗品”&#xff1f;因为大部分标准物都是使用就会磨损的&#xff0c;甚至不少标准物还是一次性的&#xff0c;并且这些标准物通常价格还不便宜&#xff0c;也是计量机构校准的主要成本之一&#xff0c;…

短距离无线连接“新”势力,移远通信再上新五款Wi-Fi与蓝牙模组

6月21日&#xff0c;在2024 MWC上海展前夕&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;推出代表其短距离通信技术的最新成果——覆盖Wi-Fi与蓝牙连接的五款模组新品。 该五款产品将通过稳连接、高可靠性、低功耗、多接口、高性价比等综合优势&…

基于STM32的智能环境监测系统

目录 引言环境准备智能环境监测系统基础代码实现&#xff1a;实现智能环境监测系统 4.1 数据采集模块4.2 数据处理与分析4.3 通信模块实现4.4 用户界面与数据可视化应用场景&#xff1a;环境监测与管理问题解决方案与优化收尾与总结 1. 引言 智能环境监测系统通过使用STM32嵌…