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 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. 使用方式

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

4. Gif图效果展示

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


总结

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

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

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

相关文章

IIS代理配置-反向代理

前后端分离项目&#xff0c;前端在开发中使用proxy代理解决跨域问题&#xff0c;打包之后无效。 未配置前无法访问 部署环境为windows IIS&#xff0c;要在iis设置反向代理 安装代理模块 需要在iis中实现代理&#xff0c;需要安装Application Request Routing Cache和URL重…

49.Chome浏览器有三种清缓存方式

49.Chome浏览器有三种清缓存方式&#xff1a;正常重新加载、硬件重新加载、清空缓存并硬性重新加载 1、【正常重新加载】 触发方式&#xff1a;①F5  ②CtrlR  ③在地址栏上回车  ④点击链接 如果缓存不过期会使用缓存。这样浏览器可以避免重新下载JavaScript文件、图像、…

创新实训2024.05.01日志:document-loaders

在建立易学知识库的过程中&#xff0c;仅仅有向量数据库以及词嵌入模型、分词器是不够的&#xff0c;因为我们有大量的非结构化文本&#xff08;如doc,pdf&#xff09;或者是图片需要上传&#xff08;例如pdf里面有图片&#xff09;&#xff0c;此时词嵌入无法直接向向量数据库…

自学鸿蒙HarmonyOS的ArkTS语言<三>路由跳转及传参

【官方文档传送门】 一、导入模块 import router from ohos.router二、新增页面配置 三、常用api 1、跳转到应用内的指定页面 build() {Row() {Button(下一页).onClick(() > {router.pushUrl({url: pages/Index2,params: {name: test}})})}.height(100%)}2、用应用内的某…

SpringBoot配置第三方专业缓存技术jetcache方法缓存方案

jetcache方法缓存 我们可以给每个方法配置缓存方案 JetCache 是一个基于 Java 的缓存库&#xff0c;支持多种缓存方案和缓存策略&#xff0c;主要用于提升应用程序的性能和响应速度。它提供了多种缓存模式和特性&#xff0c;可以根据需求选择合适的缓存方案。 JetCache 的主…

在WordPress上添加亚马逊联盟链接的三种方法

在互联网快速发展的今天&#xff0c;很多人都希望通过网络来增加收入&#xff0c;而加入亚马逊联盟计划&#xff08;Amazon Associates&#xff09;无疑是一个不错的选择。如果你有一个WordPress网站&#xff0c;那么在文章中添加亚马逊联盟链接是个很好的变现方式。今天&#…

钡铼BL110在智慧气象站实现Modbus转MQTT无线接入主流云

随着物联网&#xff08;IoT&#xff09;技术的发展&#xff0c;各行各业都在积极探索将智能设备与云平台相结合&#xff0c;以提升系统的智能化和自动化水平。智慧气象站作为其中重要的一环&#xff0c;通过实时监测环境数据&#xff0c;为农业、交通、航空等行业提供精准的气象…

Idea Git中 unversioned files的处理

项目中&#xff0c;使用git commit命令可以查看当前所在的分支&#xff0c;以及当前改动的文件&#xff0c;可以使用快捷键Alt 0打开/关闭&#xff1b;如下图所示&#xff0c; 可以看到分成了两个不同的区域&#xff0c; Changes 表示有改动的文件&#xff0c;包括修改、新增…

apache activeMq

https://blog.csdn.net/qq_29651203/article/details/108487924 游览器输入地址: http://127.0.0.1:8161/admin/ 访问activemq管理台 账号和密码默认为: admin/admin# yml配置的密码也是如下的密码 activemq:url: failover:(tcp://localhost:61616)username: adminpassword: ad…

哪个充电宝牌子好?性价比高与质量好并存!热门充电宝推荐!

随着科技的不断进步&#xff0c;我们的日常生活越来越依赖于便携式电子设备。然而&#xff0c;电池续航问题始终是这些设备的一大软肋。为了确保我们的智能手机、平板电脑、甚至是智能手表在忙碌的日子里始终有电&#xff0c;一个可靠的充电宝成为了我们的必备之选。面对市场上…

VoIP Hopper一键分析VoIP 网络信息(KALI工具系列二十九)

目录 1、KALI LINUX 简介 2、VoIP Hopper工具简介 3、信息收集 3.1 目标主机IP 3.2kali的IP 4、操作实例 4.1 VLAN 跳跃攻击 4.2 指定MAC地址 4.3 设置参数 5、总结 1、KALI LINUX 简介 Kali Linux 是一个功能强大、多才多艺的 Linux 发行版 &#xff0c;广泛用于网络…

flask实战之模板实现公共导航

基础实现 目标 在Flask中&#xff0c;使用模板继承和块&#xff08;blocks&#xff09;可以方便地提取公共导航菜单&#xff0c;使得您可以在多个页面上重用相同的导航结构。以下是一个基本示例&#xff0c;展示如何创建一个包含公共导航菜单的模板&#xff1a; 创建基础模板…

前端菜鸡学习日记 -- 关于pnpm

哈咯哇大家&#xff0c;我又来了&#xff0c;最近稍微悠闲一些&#xff0c;所以就趁着这个机会学习一些新的知识&#xff0c;今天就是碰巧遇到了pnm&#xff0c;这个可以看作是npm的升级版本&#xff0c;比npm要快&#xff0c;用起来也更得劲更迅速 官网地址&#xff1a;https…

【CT】LeetCode手撕—103. 二叉树的锯齿形层序遍历

目录 题目1- 思路2- 实现⭐103. 二叉树的锯齿形层序遍历——题解思路 2- ACM实现 题目 原题连接&#xff1a;103. 二叉树的锯齿形层序遍历 1- 思路 二叉树的层序遍历&#xff0c;遇到奇数时&#xff0c;利用 Collections.reverse() 翻转即可 2- 实现 ⭐103. 二叉树的锯齿形层…

优惠卷秒杀(并发问题)

Redis实战篇 | Kyles Blog (cyborg2077.github.io) 目录 一、Redis实现全局唯一id 二、添加优惠卷 三、实现秒杀下单 四、解决超卖问题&#xff08;库存为负&#xff09; 乐观锁解决超卖问题&#xff08;CAS法&#xff09; 五、实现一人一单 ​编辑 悲观锁解决一人一单问题…

QT绘画仪表盘

代码一步一步讲&#xff0c;就不写用啥之类的了&#xff0c;暗部走来&#xff0c;自己找使用的类以及使用方法 1、创建工程 2、重载paintEvent #include <QMainWindow> #include <QPainter> #include <QPaintEvent> protected:virtual void paintEvent(QP…

QT自定义标题栏窗口其一:实现拖动及可拉伸效果

1、效果 2、核心代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(paren

前端新手小白的Vue3入坑指南

昨天有同学说想暑假在家学一学Vue3&#xff0c;问我有没有什么好的文档&#xff0c;我给他找了一些&#xff0c;然后顺带着&#xff0c;自己也写一篇吧&#xff0c;希望可以给新手小白们一些指引&#xff0c;Vue3欢迎你。 目录 1 项目安装 1.1 初始化项目 1.2 安装初始化依…

redis哨兵模式下业务代码连接实现

目录 一&#xff1a;背景 二&#xff1a;实现过程 三&#xff1a;总结 一&#xff1a;背景 在哨兵模式下&#xff0c;真实的redis服务地址由一个固定ip转变为可以变化的ip,这样我们业务代码在连接redis的时候&#xff0c;就需要判断哪个主redis服务地址&#xff0c;哪个是从…

火绒安全删除explorer.exe文件造成windows系统异常的问题

问题 过程是这样的&#xff0c;电脑在使用过程中突然就变成了黑色的&#xff0c;任务栏、桌面等都消失了&#xff0c;只有部分程序的窗口。具体如下&#xff1a; 因为&#xff0c;在变化的时候&#xff0c;我有瞟到一眼有个火绒的气泡消息&#xff0c;就感觉是火绒错误的删除…