You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 如何实现可注册的动态导航菜单项?

动态添加侧边菜单项实现方案

方案1:Context + 自定义Hook(最推荐,符合React设计范式)

核心思路是用React Context存储菜单列表状态,将添加/删除菜单的方法通过Context统一暴露,再封装自定义Hook给业务组件调用,不需要直接对外暴露setState方法。

步骤1:定义菜单Context

import { createContext, useContext, useState, useMemo, useEffect } from 'react';

const SideMenuContext = createContext();

// 菜单Provider组件,需要包裹整个应用或路由外层
export function SideMenuProvider({ children }) {
  const [menuItems, setMenuItems] = useState([]);

  const sideMenu = useMemo(() => ({
    addMenuItem: (label, path, options = {}) => {
      setMenuItems(prev => {
        // 避免重复添加同路径菜单
        if (prev.some(item => item.path === path)) return prev;
        return [...prev, { label, path, ...options }];
      });
    },
    removeMenuItem: (path) => {
      setMenuItems(prev => prev.filter(item => item.path !== path));
    },
    menuItems
  }), []);

  return (
    <SideMenuContext.Provider value={sideMenu}>
      {children}
    </SideMenuContext.Provider>
  );
}

// 封装自定义Hook,业务组件直接调用即可
export function useSideMenu() {
  return useContext(SideMenuContext);
}

步骤2:实现侧边菜单组件

import { Link } from 'react-router-dom';
import { useSideMenu } from './SideMenuContext';

export default function SideMenu() {
  const { menuItems } = useSideMenu();
  return (
    <div className="side-menu">
      {menuItems.map(item => (
        <Link key={item.path} to={item.path}>
          {item.label}
        </Link>
      ))}
    </div>
  );
}

步骤3:根组件引入Provider

import { BrowserRouter } from 'react-router-dom';
import { SideMenuProvider } from './SideMenuContext';
import SideMenu from './SideMenu';

function App() {
  return (
    <BrowserRouter>
      <SideMenuProvider>
        <SideMenu />
        {/* 其他路由、业务组件放在此处 */}
      </SideMenuProvider>
    </BrowserRouter>
  );
}

业务组件用法(和你预期的写法基本一致)

import { Route } from 'react-router-dom';
import { useSideMenu } from './SideMenuContext';

function SomeRandomComponent() {
  const sideMenu = useSideMenu();
  const targetRoute = `my/route`;

  // 放在useEffect中避免重复渲染时多次触发添加
  useEffect(() => {
    sideMenu.addMenuItem('Link label', targetRoute);
    // 组件卸载时可选移除对应菜单
    return () => sideMenu.removeMenuItem(targetRoute);
  }, [sideMenu, targetRoute]);

  return <Route path={targetRoute}>
      <div>
          My page
      </div>
  </Route>
}

方案2:全局事件总线(轻量项目可选)

如果项目规模很小不想引入Context,可以用发布订阅模式实现,侵入性更低:

// sideMenu.js 全局公共文件
const listeners = new Set();
let menuItems = [];

export const sideMenu = {
  addMenuItem(label, path) {
    if (menuItems.some(item => item.path === path)) return;
    menuItems = [...menuItems, { label, path }];
    listeners.forEach(listener => listener([...menuItems]));
  },
  subscribe(listener) {
    listeners.add(listener);
    listener([...menuItems]);
    // 返回取消订阅方法
    return () => listeners.delete(listener);
  }
}

侧边菜单组件订阅菜单变化:

import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { sideMenu } from './sideMenu';

export default function SideMenu() {
  const [menuItems, setMenuItems] = useState([]);

  useEffect(() => {
    const unsubscribe = sideMenu.subscribe(setMenuItems);
    return unsubscribe;
  }, []);

  return (
    <div className="side-menu">
      {menuItems.map(item => (
        <Link key={item.path} to={item.path}>{item.label}</Link>
      ))}
    </div>
  );
}

业务组件直接导入sideMenu调用addMenuItem即可,用法和你给出的示例完全一致。

方案3:全局挂载方法(仅适合临时快速实现,不推荐生产环境使用)

直接将操作方法挂载到window全局对象,实现最简单但会污染全局命名空间:

// SideMenu.jsx
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';

export default function SideMenu() {
  const [menuItems, setMenuItems] = useState([]);

  useEffect(() => {
    window.sideMenu = {
      addMenuItem: (label, path) => {
        setMenuItems(prev => {
          if (prev.some(item => item.path === path)) return prev;
          return [...prev, { label, path }];
        })
      }
    };
    return () => delete window.sideMenu;
  }, []);

  return (
    <div className="side-menu">
      {menuItems.map(item => <Link key={item.path} to={item.path}>{item.label}</Link>)}
    </div>
  )
}

业务组件直接调用window.sideMenu.addMenuItem即可。

内容的提问来源于stack exchange,提问作者TOPKAT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 14:36:07