reactjs 在React(Next.js)中是否有一种通用的方法来构建和维护嵌套菜单?

72qzrwbm  于 8个月前  发布在  React
关注(0)|答案(1)|浏览(65)

在过去的几天里,我一直在做一个带有导航栏的项目,用TypeScript和React(Next.js)实现。到目前为止,我一直使用单级导航,但我正在向多级导航菜单迈进,我需要一些帮助。

初始代码:单级菜单

我最初写的代码是一个单级菜单,即一个简单的导航栏没有子菜单,只包含顶层的链接。代码如下:

1)型号/类型定义

// @/models/NavLinks.d.ts

export interface NavItem {
  title: string;
  link: string;
  ariaLabel?: string;
}

export interface NavItemCollection {
  title?: string; // Titles are used in Sidebar, not in main Navbar in this example
  items: Array<NavItem>;
}

export type NavItemCollections = NavItemCollection[];

字符串

2)数据:

// @/data/MainNav.ts

import { NavItemCollections } from "@/models/NavLinks";

export const NavLinks: NavItemCollections = [
  {
    items: [
      { title: "About", link: "/about" },
      { title: "Security", link: "/security" },
      { title: "Blog", link: "/blog" },
    ],
  },
];

3)用法:

// @/components/site/Navbar.tsx;

import Link from "next/link";
import { NavItemCollections } from "@/models/NavLinks";
import styles from "@/styles/Navbar.module.scss";

export interface NavbarProps {
  links: NavItemCollections;
}

export const Navbar = (props: NavbarProps) => {
  return (
    <>
      <nav>
       {props.links.map((collection) =>
          collection.items.map((item, index) => (
            <Link href={item.link} key={index}>
              <a className={styles.link}>{item.title}</a>
            </Link>
          ))
        )}
      </nav>
    </>
  );
}


.然后使用指定的prop传递链接。即父组件中的<Navbar links={links} />
P.S.请记住,Next.js要求我将<a> Package 在<Link>中。

我的目标:多级菜单

我个人的目标是重构整个菜单,这样我(和其他人)就有一种统一的方法来实现一个结构良好、类型良好的导航菜单,可以在多个地方使用。
为了做到这一点,我从VuePress 2借用了一些代码,它在默认主题中使用了类似的东西。

1)型号/类型定义

// @/models/NavLinks.d.ts

/**
 * Base nav item, displayed as text
 */
export interface NavItem {
  text: string;
  ariaLabel?: string;
}

/**
 * Base nav group, has nav items children
 */
export interface NavGroup<T> extends NavItem {
  children: T[];
}

/**
 * NavLink, i.e. inherited from NavItem, but extended with link properties.
 */
export interface NavLink extends NavItem {
  link: string;
  rel?: string;
  target?: string;
}

/**
 * Navbar Contents
 */
export type NavbarItem = NavLink;
export type NavbarGroup = NavGroup<NavbarGroup | NavbarItem | string>;
export type NavbarContents = (NavbarItem | NavbarGroup | string)[];

2)数据

在我们的新模型中,我们可以创建一个多级菜单,比如带子菜单的菜单项,带子菜单的菜单项和可选的子菜单项,等等。换句话说:一个嵌套的导航结构。酷。

// @/data/MainNav.ts

import { NavbarContents } from "@/models/NavLinks";

export const NavLinks: NavbarContents = [
  { text: "About", link: "/about" },
  { text: "Security", link: "/security" },
  { text: "Blog", link: "/blog" },
  {
    text: "Professional",
    children: [
      { text: "Individual", link: "/professional" },
      { text: "Business", link: "/business" },
    ],
  },
];

3)用法:

这就是我卡住的地方。如果我使用与前面所示相同的.map()方法,我会得到以下错误:

Property 'map' does not exist on type 'string | NavLink | NavbarGroup'.
Property 'map' does not exist on type 'string'.ts(2339)


这个错误是很明显的,因为你不能在string类型上使用.map,但是我应该用什么来代替呢?我试图使用typeof来判断它是否是一个字符串。然后只要呈现字符串就可以了。

<nav className={styles.navlinks}>
         {props.links.map((collection) =>
           typeof (collection) === "string" ?
             <span className="string">{collection}</span>
             : (...)


剩下的不是NavLink就是NavbarGroup,不知道怎么实现。
问题是:我如何使用上面的类型定义和数据来拥有一个嵌套菜单?

4nkexdtk

4nkexdtk1#

需要在它要求的地方添加类型信息。你可以使用递归方法来呈现菜单,如下所示。

import { NavLinks } from "./MainNav";
import { NavbarContents, NavbarItem, NavbarGroup } from "./NavLinks";

export default function App() {
  const renderMenu = (links: NavbarContents) => {
    return links.map((menuItem: NavbarItem | NavbarGroup | string) => (
      <div style={{ marginLeft: "10px" }}>
        <a href={menuItem?.link}>{menuItem?.text}</a>
        {menuItem?.children && renderMenu(menuItem?.children)}
      </div>
    ));
  };

  return <div>{renderMenu(NavLinks)}</div>;
}

字符串
你可以根据你的菜单项类型来扩展这个代码。当你这样做的时候,它将被设置为开关情况,而不是<a href={menuItem?.link}>{menuItem?.text}</a>,它适用于NavbarItem
代码沙盒=> https://codesandbox.io/s/elated-smoke-zzxt4?file=/src/App.tsx

相关问题