React(Next.js)中如何通用构建维护支持TypeScript的嵌套导航菜单
解决方案
第一步:添加类型守卫辅助函数
首先实现3个类型守卫,让TypeScript可以准确识别不同的导航项类型,消除类型报错,你可以把这部分代码放在类型定义文件NavLinks.d.ts中,或者Navbar组件文件顶部:
export function isNavGroup(item: unknown): item is NavbarGroup { return typeof item === 'object' && item !== null && 'children' in item } export function isNavLink(item: unknown): item is NavbarItem { return typeof item === 'object' && item !== null && 'link' in item } export function isNavString(item: unknown): item is string { return typeof item === 'string' }
第二步:实现递归渲染逻辑
因为菜单支持无限层级嵌套,我们需要用递归逻辑渲染每一层导航项,完整的Navbar组件代码如下:
// @/components/site/Navbar.tsx import Link from "next/link"; import { NavbarContents, isNavGroup, isNavLink, isNavString } from "@/models/NavLinks"; import styles from "@/styles/Navbar.module.scss"; export interface NavbarProps { links: NavbarContents; } // 递归渲染导航项的辅助函数 const renderNavItem = (item: NavbarContents[number], key: number | string) => { // 处理字符串类型的导航项 if (isNavString(item)) { return <span key={key} className={styles.stringItem}>{item}</span> } // 处理分组类型(带子菜单的导航项) if (isNavGroup(item)) { return ( <div key={key} className={styles.navGroup}> <span className={styles.groupTitle}>{item.text}</span> <div className={styles.groupChildren}> {item.children.map((child, idx) => renderNavItem(child, idx))} </div> </div> ) } // 处理普通跳转链接类型的导航项 if (isNavLink(item)) { return ( <Link href={item.link} key={key}> <a className={styles.link} aria-label={item.ariaLabel} rel={item.rel} target={item.target} > {item.text} </a> </Link> ) } return null } export const Navbar = (props: NavbarProps) => { return ( <nav className={styles.nav}> {props.links.map((item, index) => renderNavItem(item, index))} </nav> ); }
第三步:补充菜单样式(可选)
你可以在Navbar.module.scss里补充下拉交互样式,实现hover显示子菜单的效果,示例参考:
.nav { display: flex; gap: 2rem; align-items: center; } .navGroup { position: relative; cursor: pointer; .groupTitle { padding: 0.5rem 0; } .groupChildren { position: absolute; top: 100%; left: 0; min-width: 120px; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); display: none; flex-direction: column; padding: 0.5rem 0; } &:hover .groupChildren { display: flex; } } .link { padding: 0.5rem 1rem; text-decoration: none; color: inherit; } .stringItem { padding: 0.5rem 0; color: #666; }
以上实现完全兼容你定义的类型规范,支持无限层级嵌套,类型安全,可直接复用到侧边栏等其他导航场景。
内容的提问来源于stack exchange,提问作者Ricardo
相关产品推荐
相关产品推荐

