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

文章目录

  • 前言
  • 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图效果展示

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


总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

13.1.k8s集群的七层代理-ingress资源(进阶知识)

目录 一、ingress概述 1.前言 2.问题 3.ingress资源 二、ingress-nginx是什么 三、ingress-nginx 实现原理 四、部署ingress-nginx 1.获取部署文件 ingress-nginx.yaml 2.部署ingress-nginx 3.检查部署是否成功 五、编写使用Ingress样例代码 1.Ingress资源对象yaml文…

让生产管理变简单

随着业务的发展&#xff0c;工厂每天要处理很多订单&#xff0c;还要统筹安排各部门工作以及协调上下游加工企业&#xff0c;生产管理问题也随之而来。 1.销售订单评审困难、无法及时抓取到历史数据做参考。由于数据的不及时性、不准确性无法为正常的生产和采购提供数据支撑。同…

佳裕达物流:披露风险,负债1.26亿,营收下滑,净利润下滑-5925.93%

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 一、佳裕达物流公司介绍 佳裕达物流成立于2009年&#xff0c;总部位于广东省深圳市&#xff0c;是中国领先的端到端供应链解决方案供应商之一。 现已发展成为立足粤港澳大湾区、面向全国、布局全球的大型AAAA级综合服务型…

【知识图谱】基于neo4j开发的信息化文档分析系统(源码)

一、项目介绍 一款全源码&#xff0c;可二开&#xff0c;可基于云部署、私有部署的企业级知识库云平台&#xff0c;一款让企业知识变为实打实的数字财富的系统&#xff0c;应用在需要进行文档整理、分类、归集、检索、分析的场景。 为什么建立知识库平台&#xff1f; 助力企业…

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义&#xff0c;意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换&#xff0c;神经网络能够学习数据的特征和模式&#xff0c;实现对复杂数据的建模和预测。(我们可以借助人类的神经元模…

[保姆级教程]在uniapp中使用vant框架

文章目录 导文安装 Vant在uniapp项目中的pages.json中配置easycom&#xff0c;实现组件的自动按需引入&#xff1a;在页面中使用Vant Weapp组件&#xff0c;例如使用按钮组件&#xff08;Button&#xff09;&#xff1a;其他安装报错官网地址 导文 在 uni-app 中使用 Vant 框架…

JavaSE (Java基础):结构体(流程控制)

5 结构体&#xff08;流程控制&#xff09; 5.1 顺序结构 下面我是在某B上面一个大佬up狂神学习时截图的&#xff0c;关于这个基本结构顺序结构的介绍&#xff0c;这个结构体没什么好说的。 package com.zlx.struct; //顺序结构 public class OrderDemo01 {public static void…

重磅消息:ONLYOFFICE8.1版本桌面编辑器发布:功能完善的 PDF 编辑器、幻灯片版式、改进从右至左显示、新的本地化选项等

目录 ONLYOFFICE介绍 PDF 编辑器 功能全面的 PDF 编辑器 文本编辑 页面处理 &#xff08;添加、旋转、删除&#xff09; 插入和调整各种对象&#xff0c;例如表格、形状、文本框、图像、TextArt、超链接、方程等。 此外 PDF 表单 文本文档编辑器更新内容 页面颜色 页面…

从移动、桌面端到AR/VR:HOOPS Visualize如何实现卓越的3D模型可视化?

在当今迅速发展的技术环境中&#xff0c;高性能、跨平台的图形引擎是工程应用程序开发的核心需求。HOOPS Visualize作为一款领先的3D图形SDK&#xff0c;为桌面、移动和AR/VR应用程序提供了强大的2D和3D图形支持。其设计旨在实现工程应用程序中的高性能可视化&#xff0c;确保在…

大腾智能,基于云原生的国产工业协同平台

大腾智能是一家基于云原生的国产工业软件与数字化协同平台&#xff0c;专注于推动企业数字化转型与升级&#xff0c;为企业提供一系列专业、高效的云原生数字化软件及方案&#xff0c;推动产品设计、生产及营销展示的革新&#xff0c;实现可持续发展。 大腾智能旗下产品 3D模型…

益百分4.0版益生君重磅来袭,为您保驾护航

益百分4.0版益生君重磅来袭&#xff0c;为您保驾护航 暑期来临&#xff0c;很多人们终于等来了一年中最幸福的时刻&#xff0c;三五成群、结伴旅游成为他们选择欢度暑假的方式。 全国各地的旅游景点也迎来了旺季&#xff0c;各大旅游公司也推出了各种各样的旅游团购活动&#x…

iptables(4)规则匹配条件(源、目、协议、接口、端口)

简介 前面我们已经介绍了iptables的基本原理,表、链,数据包处理流程。如何查询各种表的信息。还有基本的增、删、改、保存的基础操作。 经过前文介绍,我们已经能够熟练的管理规则了,但是我们只使用过一种匹配条件,就是将”源地址”作为匹配条件。那么这篇文章中,我们就来…

我真是反感那些叉劈。

再转一下&#xff0c;想看的自己提取吧。

最强铁基超导磁体诞生!科学家基于机器学习设计新研究体系,磁场强度超过先前记录2.7倍

超导现象&#xff0c;自 1911 年被发现以来&#xff0c;始终保持着前沿性与高价值&#xff0c;吸引了大批学者投身其研究中。超导现象是指某些材料在低于特定温度时电阻突然降为零&#xff0c;这不仅是材料学的革命性突破&#xff0c;也为电力传输、磁悬浮交通和医疗成像等领域…

中电金信:财务公司数字化转型的“求索”路径与实践分享

随着全球商业环境的快速变化和国家对数字化发展的高度重视&#xff0c;数字化转型已成为推动经济高质量发展的关键。央国企财务公司的数字化建设程度较商业银行存在很大差距&#xff0c;数字化转型“路漫漫其修远兮”。如何借“数字之力”实现世界一流财务管控体系的总目标&…

【Python机器学习】NMF——将NMF应用于模拟信号数据

假设我们对一个信号感兴趣&#xff0c;它是由三个不同信号源合成的&#xff1a; import matplotlib.pyplot as plt import mglearnSmglearn.datasets.make_signals() plt.figure(figsize(6,1)) plt.plot(S,-) plt.xlabel(Time) plt.ylabel(Signal) plt.show()不幸的是&#xff…

IDEA中 pom.xml 设置自动提示

IDEA中 pom.xml 自动提示 IDEA中 pom.xml 自动提示设置如下&#xff1a; file–>Settings–>Build,Execution…–>Build Tools–>Maven–>Repositories 会看到类似表格的画面&#xff0c;内容是你的maven地址&#xff0c;选中后&#xff0c;右边有个Update的按…

深度学习-CPGNet部署运行

1、训练代码部署运行 1.1 部署 新建虚拟环境&#xff1a; conda create --name cpgnet-new python3.7 安装依赖&#xff1a; CUDA11.3 Pytorch1.12.1 PyYAML5.4.1 scipy1.3.1 nuscenes pytz rospkg pyquaternion opencv-python scikit-learn matplotlib tqdm cachetools编译安…

振弦式渗压计:土壤力学与地下水流动研究的关键工具

当谈论到地下水流动和土壤力学时&#xff0c;振弦式渗压计是一种至关重要的工具。这篇文章将探讨振弦式渗压计的原理、工作方式以及其在土壤力学和地下水流动研究中的重要性。 振弦式渗压计的原理 振弦式渗压计利用了振动传感器和压力传感器的组合来测量土壤中的水压力。其基本…