این آموزش یک نسخه ی ساده شده از آموزشی هست که Ibadehin Mojeed نوشته.

اگر توضیحات کامل میخوایید، باید این مقاله رو بخونید. ایشون همچنین یک مقاله ی دیگه هم برای درست کردن منو با CSS داره. برای خوندنش اینجا کلیک کنید .

در آخر این آموزش یک منو مثل این داریم:

Final-outcome-react-multilevel-dropdown-menu-project

یک راه خوب برای اینکه یک منوی چند سطحی داشته باشیم اینه که از تابع بازگشتی استفاده کنیم. همون طور که توی GeeksForGeeks توضیح داده شده:

تابع بازگشتی را می توان به عنوان روتینی تعریف کرد که به طور مستقیم یا غیرمستقیم خود را فراخوانی می کند. به عبارت دیگر، یک تابع بازگشتی تابعی است که یک مسئله را با حل نمونه های کوچکتر از همان مسئله حل می کند. این تکنیک معمولاً در برنامه نویسی برای حل مسائلی استفاده می شود که می توانند به مسائل فرعی ساده تر و مشابه تقسیم شوند.

اساسا تابع بازگشتی، تابعی هست که خودش رو فراخوانی میکنه، یک چیزی مثل این:

myRecursiveFunction(){
   // some code
   myRecursiveFunction()
   // some code
}

خب شاید بگید که اینجوری یک حلقه‌ی بینهایت درست میشه، این حرف درسته اما نه اگر یک شرط بنویسیم که فراخوانی تابع رو در زمان خاصی متوقف کنه. یک چیزی مثل این:

myRecursiveFunction(){
   // some code
   if(true){
    return
   }
   myRecursiveFunction()
   // some code
}

همچنین خوبه بدونید که هر مسئله‌ای که با تابع بازگشتی حل بشه، همیشه میتونه با حلقه‌های for هم حل بشه، اما در اکثر موارد، تابع بازگشتی کارمون رو خیلی راحت تر میکنه. خب در شرایط ما، فرض کنید یک آرایه از منو‌ها و زیر منوها داریم. به این شکل:

[
  {
    title: "Home",
    url: "/",
  },
  {
    title: "Services",
    url: "/services",
    submenu: [
      {
        title: "web design",
        url: "web-design",
      },
      {
        title: "web development",
        url: "web-dev",
        submenu: [
          {
            title: "Frontend",
            url: "frontend",
          },
          {
            title: "Backend",
            submenu: [
              {
                title: "NodeJS",
                url: "node",
              },
              {
                title: "PHP",
                url: "php",
              },
            ],
          },
        ],
      },
      {
        title: "SEO",
        url: "seo",
      },
    ],
  },
  {
    title: "About",
    url: "/about",
    submenu: [
      {
        title: "Who we are",
        url: "who-we-are",
      },
      {
        title: "Our values",
        url: "our-values",
      },
    ],
  },
];

ما میتونیم یک تابع بنویسیم که وظیفه‌اش نمایش همه‌ی نوبار ( navbar ) باشه، من اسمش رو گذاشتم Header همونطور که Ibadehin Mojeed هم همین کارو کرده، دلیل‌اش هم اینه که تگ هدر‌ مون که داخل این تابع هست دور همه چیز پیچیده، اما شما اسمشو هرچی بخوایید میتونید بزارید. درضمن توی این تابع برای صفحه نمایش های کوچیک از usestate استفاده کردم.

export default function Header() {
  const [toggle, setToggle] = useState(false);
  return (
    <header className={` ${toggle ? "active" : ""} `}>
      <div className="nav-area">
        <Link href="/" className="logo">
          logo
        </Link>
        {menuItems.length ? (
          <>
            <nav>
              <ul className="menus">
                {menuItems.map((menu, index) => {
                  const depthLevel = 0;
                  return (
                    <MenuItems
                      items={menu}
                      key={index}
                      depthLevel={depthLevel}
                    />
                  );
                })}
              </ul>
            </nav>
            <div
              className="menuToggle"
              onClick={() => setToggle(toggle === true ? false : true)}
            ></div>
          </>
        ) : null}
      </div>
    </header>
  );
}

function MenuItems({ items, depthLevel }) {
  const [dropdown, setDropdown] = useState(false);

  function onMouseEnter() {
    window.innerWidth > 960 && setDropdown(true);
  }

  function onMouseLeave() {
    window.innerWidth > 960 && setDropdown(false);
  }

  return (
    <li
      className="menu-items"
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      {items.submenu ? (
        <>
          <button
            type="button"
            aria-haspopup="menu"
            aria-expanded={dropdown ? "true" : "false"}
            onClick={() => setDropdown((prev) => !prev)}
          >
            {items.title}{" "}
            {depthLevel > 0 ? <span>&raquo;</span> : <span className="arrow" />}
          </button>
          <Dropdown
            depthLevel={depthLevel}
            submenus={items.submenu}
            dropdown={dropdown}
          />
        </>
      ) : (
        <Link href={items.url}>{items.title}</Link>
      )}
    </li>
  );
}

function Dropdown({ depthLevel, submenus, dropdown }) {
  depthLevel = depthLevel + 1;
  const dropdownClass = depthLevel > 1 ? "dropdown-submenu" : "";

  return (
    <ul className={` dropdown ${dropdownClass} ${dropdown ? "show" : ""} `}>
      {submenus.map((submenu, index) => (
        <MenuItems items={submenu} key={index} depthLevel={depthLevel} />
      ))}
    </ul>
  );
}

همونطور که میبینید یک تابع MenuItems داخل تابع Header هست، که وظیفه‌اش ساختن زیر منو ( ها ) داخل منو ( ها ) هست. تابع MenuItems همون تابع بازگشتی ما هست. این تابع خودش رو به صورت غیر مستقیم و توسط تابع Dropdown فراخوانی میکنه و ? items.submenu همون شرط مبنا هست، اگر حاصل‌اش غلط باشه، اون وقت یک لینک درست میکنه، اما اگر حاصل‌اش درست باشه یک button درست میکنه ( که کاربر میتونه روش کلیک یا هاور کنه تا زیر منو ( ها ) رو ببینه ) و بعد دوباره خودش رو توسط تابع Dropdown فراخوانی میکنه تا دوباره یک Link درست کنه یا یک button.

این هم کد CSS تا کد مون بیشتر حس یک نویگیشن ( Navigation ) بده:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
}

header {
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.07), 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  color: #212529;
}

.nav-area {
  display: flex;
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px 20px;
}

.logo {
  text-decoration: none;
  font-size: 25px;
  color: inherit;
  margin-right: 20px;
}

.menus {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  list-style: none;
}

.menu-items {
  position: relative;
  font-size: 14px;
}

.menu-items a {
  display: block;
  font-size: inherit;
  color: inherit;
  text-decoration: none;
}

.menu-items button {
  display: flex;
  align-items: center;
  color: inherit;
  font-size: inherit;
  border: none;
  background-color: transparent;
  cursor: pointer;
  width: 100%;
}

button span {
  margin-left: 3px;
}

.menu-items > a,
.menu-items button {
  text-align: left;
  padding: 0.7rem 1rem;
}

.menu-items a:hover,
.menu-items button:hover {
  background-color: #f2f2f2;
}

.arrow::after {
  content: "";
  display: inline-block;
  margin-left: 0.28em;
  vertical-align: 0.09em;
  border-top: 0.42em solid;
  border-right: 0.32em solid transparent;
  border-left: 0.32em solid transparent;
}

.dropdown {
  position: absolute;
  left: 0;
  left: auto;
  box-shadow: 0 10px 15px -3px rgba(46, 41, 51, 0.08), 0 4px 6px -2px rgba(71, 63, 79, 0.16);
  font-size: 0.875rem;
  z-index: 9999;
  min-width: 10rem;
  padding: 0.5rem 0;
  list-style: none;
  background-color: #fff;
  border-radius: 0.5rem;
  display: none;
}

.dropdown.show {
  display: block;
}

.dropdown .dropdown-submenu {
  position: absolute;
  left: 100%;
  top: -7px;
}

@media (max-width: 1300px) {
  nav {
    display: none;
    max-height: 50vh;
    overflow-x: hidden;
    overflow-y: auto;
  }

  .menus {
    padding-left: 0px;
  }

  .dropdown .dropdown-submenu {
    position: initial;
  }
  .dropdown {
    position: relative;
    border-radius: 0;
    box-shadow: none;
  }
  header nav {
    position: absolute;
    width: 100%;
    top: 70px;
    left: 0;
    background-color: #e5e2ff;
  }
  header.active nav {
    display: initial;
  }
  header nav ul li {
    width: 100%;
  }
  header nav ul li ul {
    position: relative;
    width: 100%;
    left: 0;
  }
  header ul li ul li ul {
    top: 0;
    left: 0;
  }
  header nav ul li:hover ul li {
    background-color: #ffffff;
  }

  .menuToggle {
    position: relative;
    width: 40px;
    height: 50px;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-left: auto;
  }

  .menuToggle::before {
    content: "";
    position: absolute;
    width: 100%;
    height: 3px;
    background-color: #000000;
    transform: translateY(-12px);
    box-shadow: 0 12px #000000;
  }

  .menuToggle::after {
    content: "";
    position: absolute;
    width: 100%;
    height: 3px;
    background-color: #000000;
    transform: translateY(12px);
  }
  header.active .menuToggle::before {
    transform: rotate(45deg);
    box-shadow: 0 0 #000000;
  }
  header.active .menuToggle::after {
    transform: rotate(315deg);
  }

  .nav--options-large {
    display: none;
  }

  .nav--options-small {
    display: initial;
  }
}

در نهایت

امیدوارم مفید بوده باشه، اگر شما همچنان متوجه نشدید که یک تابع بازگشتی چیکار میکنه، کاملا طبیعی هست، چون من به صورت مختصر توضیح‌اش دادم، پس خوبه که برید و سرچ کنید.