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;// overflow: auto;// overscroll-behavior: contain;}}
`;`;

(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 issuesif (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 changeshandleChange();// Listen matchMediaif (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. 使用方式

// 引入组件
import Loading from "@/components/Loading";
// 使用
<Loading size="small" /> {/* 小尺寸loading */}
<Loading /> {/* 默认尺寸loading */}
<Loading size="large" /> {/* 大尺寸loading */}

4. Gif图效果展示

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


总结

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

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

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

相关文章

Oracle最终还是杀死了MySQL

起因 大约15年前&#xff0c;Oracle收购了Sun公司&#xff0c;从而也拥有了MySQL&#xff0c;互联网上关于Oracle何时会“扼杀MySQL”的讨论此起彼伏。 当时流传着各种理论&#xff1a;从彻底扼杀 MySQL 以减少对 Oracle 专有数据库的竞争&#xff0c;到干掉 MySQL 开源项目&…

qt开发-07_radioButton

QRadioButton 部件提供了一个带有文本标签的单选框&#xff08;单选按钮&#xff09;。 QRadioButton 是一个可以切换选中&#xff08;checked&#xff09;或未选中&#xff08;unchecked&#xff09;状态的选项按钮。 单选框通常呈现给用户一个“多选一”的选择。也就是说&…

Emacs之复制时:禁止转换成tab符号(一百三十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

购物网站系统

摘 要 随着互联网的快速发展&#xff0c;不同的平台软件也不断涌出市场&#xff0c;在众多的平台中&#xff0c;购物网站深受人们的欢迎&#xff0c;也成为生活中不可缺少的一部分。经过对国内外购物情况的调查&#xff0c;社区购物在近几年来成为电商发展的新趋势&#xff0c…

递归乘法00

题目链接 递归乘法 题目描述 注意点 保证乘法范围不会溢出 解答思路 使用加法代替乘法&#xff0c;递归计算A * B&#xff0c;每个递归的过程加上一个A&#xff0c;且对B减1&#xff0c;直到B为0为止 代码 class Solution {public int multiply(int A, int B) {if (B 0…

C++ 教程 - 05 构建编译

文章目录 构建工具cmake安装与使用CMakeLists.txt编写使用案例 构建工具 cmake, Cross Platform Make&#xff0c; &#xff08;对C&#xff09;跨平台编译工具&#xff0c;将CMakeLists.txt 文件编译为对应的文件&#xff0c;如linux下的 Makefile&#xff0c;然后使用make命…

[WTL/Win32]_[中级]_[MVP架构在实际项目中的应用]

场景 在开发Windows和macOS的界面软件时&#xff0c;Windows用的是WTL/Win32技术&#xff0c;而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C,另一个却是Object-c。界面软件的源码随着项目功能增多而增多&#xff0c;这就会给同步Windows和macOS的功能造成很大负担…

Github 2024-06-21 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-21统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目3Python项目3Java项目2非开发语言项目2JavaScript项目1Rust项目1Dart项目1HTML项目1Vue项目1C++项目1TensorFlow: 机器学习的开源…

用户和账号

chage、useradd、passwd、usermod、userdel、groupadd、gpasswd、groupdel、groups、 用户账号初始配置文件 .bashrc .bash_profile .bash_logout finger、w、who、users chmod、chowd、umask、last 1.用户的分类 Linux 用户三种角色 超级用户&#x…

LInux驱动开发笔记(十)SPI子系统及其驱动

文章目录 前言一、SPI驱动框架二、总线驱动2.1 SPI总线的运行机制2.2 重要数据结构2.2.1 spi_controller2.2.2 spi_driver2.2.3 spi_device2.2.4 spi_transfer2.2.5 spi_message 三、设备驱动的编写3.1 设备树的修改3.2 相关API函数3.2.1 spi_setup( )3.2.2 spi_message_init( …

使用GPG来解密和加密文件详解

文章目录 使用私钥解密文件示例步骤 注意事项加密文件前提条件导入公钥加密文件输出加密文件示例步骤注意事项邮箱不是必须的情况1&#xff1a;有多个公钥情况2&#xff1a;只有一个公钥示例步骤示例1&#xff1a;指定公钥ID或邮箱地址示例2&#xff1a;密钥环中只有一个相关的…

深度学习论文: Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data

深度学习论文: Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data PDF: https://arxiv.org/abs/2401.10891.pdf 代码:https://github.com/LiheYoung/Depth-Anything PyTorch代码: http…

揭秘最酷的Matplotlib 风格库!

相信大家对Matplotlib多少有些了解&#xff0c;对于数据研究&#xff0c;最终的研究结果都尽可能利用可视化呈现&#xff0c;使其更加直观通俗易懂。而Matplotlib作为python家族中最为著名的绘图工具&#xff0c;其风格实在是有些无聊&#xff0c;有时会让人觉得科研工作者在可…

React中的JSX应该怎么用

什么是JSX JSX Javascript XML&#xff0c;JSX是一个 JavaScript 的语法扩展。 JSX可以很好地描述 UI 应该呈现出它应有交互的本质形式并且其完全可以和JavaScript融合在一起使用。而且具有 JavaScript 的全部功能。JSX 可以生成 React “元素”。 JSX代码示例&#xff1a; …

MicroBlaze IP核中Local Memory Bus (LMB)接口描述

LMB&#xff08;Local Memory Bus&#xff09;是一种同步总线&#xff0c;主要用于访问FPGA上的块RAM&#xff08;Block RAM&#xff0c;BRAM&#xff09;。LMB使用最少的控制信号和一个简单的协议&#xff0c;以保证块RAM能在一个时钟周期内被存取。所有的LMB信号都是高电平有…

【服务器03】之【Navicat完整版破解】

首先清掉电脑所有Navicat组件 虽然被卸载掉了但是没有彻底清理掉Navicat组件 在原装盘里找到Navicat清楚碎片 清空之后开始下载 navicat16 https://www.alipan.com/s/GTvP93mn3sU 点击链接保存&#xff0c;或者复制本段内容&#xff0c;打开「阿里云盘」APP &#xff0c;无需…

AI Workflow的敏捷开发:持续创新与优化的艺术

在人工智能的浪潮中&#xff0c;AI Workflow作为大模型落地的关键实践&#xff0c;正逐渐成为技术领域的新宠。然而&#xff0c;随着技术的发展&#xff0c;我们面临着一系列挑战&#xff0c;如何有效地应对这些挑战&#xff0c;实现AI Workflow的敏捷开发&#xff0c;成为了一…

fyne的VBox布局02

VBox布局02 最常用的布局是layout.BoxLayout&#xff0c;它有两种变体&#xff0c;水平和垂直。box布局将所有元素排列在单行或单列中&#xff0c;并带有可选的空格以帮助对齐。 一步一步实现一个如下界面布局&#xff0c;这个界面可以使用VBox布局来实现。 这次添加了2个复…

【C语言】自定义类型

目录 一、结构体&#xff1a; 1、结构体的声明&#xff1a; 2、结构体的自引用&#xff1a; 3、结构体变量的定义和初始化&#xff1a; 4、结构体内存对齐&#xff1a; 5、结构体传参&#xff1a; 6、位段&#xff1a; 二、枚举类型&#xff1a; 三、联合体&#xff1a…

【设计模式深度剖析】【10】【行为型】【状态模式】

&#x1f448;️上一篇:访问者模式 | 下一篇:解释器模式&#x1f449;️ 设计模式-专栏&#x1f448;️ 文章目录 状态模式定义英文定义直译如何理解呢&#xff1f; 状态模式的角色Context&#xff08;环境类&#xff09;State&#xff08;抽象状态类&#xff09;Concret…