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
相关产品推荐
相关产品推荐

