Ant Design Pro 中 menuDataRender
全面解析与应用实例
menuDataRender
是 Ant Design Pro 路由系统的核心配置项,用于动态生成导航菜单。它让你可以完全控制菜单的数据源、格式和渲染逻辑,取代默认的静态路由配置。
🧩 基本用法
// src/app.tsx
import type { RunTimeLayoutConfig } from '@umijs/max';
export const layout: RunTimeLayoutConfig = ({ initialState }) => ({
menuDataRender: () => {
// 返回菜单数据数组
return initialState?.menus || [];
}
});
🔍 核心功能与使用场景
功能 | 使用场景 | 代码示例 |
---|---|---|
动态菜单加载 | 从后端API获取菜单 | return await fetch('/api/menus') |
权限过滤 | 不同角色显示不同菜单 | filter(item => hasPermission(item)) |
菜单格式转换 | 兼容不同数据格式 | map(item => ({...item, name: item.label})) |
状态控制 | 实时更新菜单 | 结合useState/initialState |
多级菜单处理 | 递归转换子菜单 | 递归处理children字段 |
📚 完整企业级实现
1. 数据准备 (getInitialState)
export async function getInitialState() {
// 从API获取菜单数据
const fetchMenus = async () => {
const { data } = await fetch('/api/system/menus');
// 格式化菜单数据
return data.map(item => ({
path: item.url,
name: item.title,
icon: item.iconType,
access: item.permission,
children: item.children || null
}));
};
return {
menus: await fetchMenus(),
// 其他全局状态...
};
}
2. 完整menuDataRender实现
export const layout: RunTimeLayoutConfig = ({ initialState }) => ({
menuDataRender: () => {
const { menus = [], currentUser } = initialState || {};
const { permissions = [] } = currentUser || {};
// 1. 菜单权限过滤
const filterMenuByPermission = (menuItems: MenuDataItem[]) => {
return menuItems.filter(item => {
// 无权限要求或用户有权限
const hasAccess = !item.access || permissions.includes(item.access);
// 递归处理子菜单
if (item.children) {
item.children = filterMenuByPermission(item.children);
}
return hasAccess && (!item.children || item.children.length > 0);
});
};
// 2. 菜单排序
const sortMenu = (menuItems: MenuDataItem[]) => {
return [...menuItems].sort((a, b) => {
const weightA = typeof a.weight === 'number' ? a.weight : 100;
const weightB = typeof b.weight === 'number' ? b.weight : 100;
return weightA - weightB;
});
};
// 3. 特殊菜单项处理
const processedMenus = menus.map(item => {
// 添加外部链接处理
if (item.isExternal) {
return {
...item,
target: '_blank',
};
}
// 添加新窗口打开
if (item.newTab) {
return {
...item,
target: '_blank',
rel: 'noopener noreferrer',
};
}
return item;
});
// 4. 返回过滤和排序后的菜单
return sortMenu(filterMenuByPermission(processedMenus));
},
});
🚀 高级应用场景
场景1:多租户菜单隔离
menuDataRender: () => {
const { menus, currentTenant } = initialState || {};
return menus.filter(item =>
!item.tenant || item.tenant === currentTenant.id
);
}
场景2:灰度发布控制
menuDataRender: () => {
return initialState.menus.filter(item =>
!item.featureFlag || isFeatureEnabled(item.featureFlag)
);
}
场景3:动态图标加载
menuDataRender: () => {
const icons = {
dashboard: <DashboardOutlined />,
user: <UserOutlined />,
// ...其他图标映射
};
return initialState.menus.map(item => ({
...item,
icon: icons[item.icon] || <DefaultIcon />
}));
}
场景4:菜单缓存处理
menuDataRender: () => {
const now = Date.now();
// 检查本地缓存
const cachedMenus = JSON.parse(
localStorage.getItem('menuCache') || 'null'
);
const cacheExpire = localStorage.getItem('menuExpire') || '0';
if (cachedMenus && now < Number(cacheExpire)) {
return cachedMenus;
}
// 更新缓存
localStorage.setItem('menuCache', JSON.stringify(initialState.menus));
localStorage.setItem('menuExpire', String(now + 300000)); // 5分钟有效
return initialState.menus;
}
📊 菜单数据结构示例
[
{
"path": "/dashboard",
"name": "控制台",
"icon": "dashboard",
"access": "dashboard.view",
"weight": 10,
"children": [
{
"path": "/dashboard/analysis",
"name": "分析页",
"access": "analysis.view",
"weight": 1
},
{
"path": "/dashboard/workplace",
"name": "工作台",
"access": "workplace.access"
}
]
},
{
"path": "/admin",
"name": "系统管理",
"icon": "setting",
"access": "system.admin",
"weight": 100,
"featureFlag": "ADMIN_PANEL"
}
]
⚙️ 菜单项属性详解
属性 | 类型 | 说明 | 示例 |
---|---|---|---|
path | string | 路由路径 | "/user/list" |
name | string | 菜单名称 | "用户管理" |
icon | string | ReactNode | 菜单图标 | "user" 或 <UserOutlined /> |
access | string | 权限标识 | "user.manage" |
hideInMenu | boolean | 是否隐藏 | true |
hideChildrenInMenu | boolean | 隐藏子菜单 | false |
weight | number | 排序权重 | 10 |
children | MenuDataItem[] | 子菜单 | [...] |
target | string | 打开方式 | "_blank" |
featureFlag | string | 特性开关 | "NEW_FEATURE" |
tenant | string | 租户标识 | "tenant_a" |
🚨 常见问题解决
-
菜单不更新问题
// 强制刷新菜单 import { refreshInitialState } from '@umijs/max'; const updateMenus = () => { const newMenus = [...(initialState?.menus || [])]; // 修改newMenus... setInitialState({ ...initialState, menus: newMenus }); // 强制刷新布局 refreshInitialState(); }
-
性能优化
// 使用缓存提高性能 import memoize from 'lodash/memoize'; const processMenus = memoize(menus => { // 复杂的菜单处理逻辑 }); menuDataRender: () => processMenus(initialState?.menus || [])
-
菜单空状态处理
menuDataRender: () => { if (!initialState?.menus?.length) { return [{ name: '加载中...', path: '/loading', icon: <LoadingOutlined /> }]; } // ...正常处理 }
🧪 调试技巧
-
实时查看菜单数据
menuDataRender: () => { const finalMenus = ...; // 处理逻辑 console.log('Current Menu Structure:', finalMenus); return finalMenus; }
-
开发环境菜单调试页
// 只在开发环境添加 if (process.env.NODE_ENV === 'development') { menuDataRender: () => [ ...initialState.menus, { name: '菜单调试', path: '/menu-debug', routes: [ { path: '/menu-debug/data', component: './MenuDebugger' } ] } ] }
-
性能监控
menuDataRender: () => { const start = performance.now(); // 菜单处理逻辑... const duration = performance.now() - start; reportPerf('menuRender', duration); return processedMenus; }
🌐 多语言支持
import { useIntl } from '@umijs/max';
// 在layout函数内
const intl = useIntl();
menuDataRender: () => {
return initialState.menus.map(menu => ({
...menu,
name: menu.localeId ?
intl.formatMessage({ id: menu.localeId }) :
menu.name
}))
}
通过 menuDataRender
,你可以构建出高度动态化、可扩展的菜单系统,满足企业级应用对权限管理、多租户支持、灰度发布等复杂需求。在生产环境中建议配合缓存策略,保证菜单加载性能的同时保持其动态更新能力。