「已解决」umi 路由配置 icon 图标报错问题

umi框架在配置路由时遇到icon图标导致白屏的问题,报错提示Invalid hook call。解决方案是重启应用,问题解决。可能与Ant Design Icon的使用有关。

问题

当umi的路由配置中带有icon时就会白屏报错
比如这样可以正常渲染
(前面三个路由配置也是一开始加图标就报错,但是后面不知道怎么搞的不报错了)

export const route = [
  {
    path: '/',
    redirect: '/home',
  },
  {
    name: '首页',
    icon: 'HomeOutlined',
    path: '/home',
    component: './Home',
  },
  {
    name: '权限演示',
    icon: 'ControlOutlined',
    path: '/access',
    component: './Access',
  },
  {
    name: ' CRUD 示例',
    icon: 'FormOutlined',
    path: '/table',
    component: './Table',
  },
  {
    name: 'basic-list',
    // icon: 'TableOutlined',
    path: '/basicList',
    component: './BasicList',
  },
];

在这里插入图片描述
把注释去掉就白屏
在这里插入图片描述

报错

  • index.js:1 The above error occurred in the component:
  • index.js:1 Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
  • mf-dep____vendor.8579e6bb.js:284446 Uncaught TypeError: Cannot read properties of null (reading ‘useContext’)

解决

在运行的时候实时修改路由的 icon 不会生效
重新启动就可以了

思考

似乎使用 Ant-Icon 时都有这个问题!

import { join } from 'node:path'; import { defineConfig } from '@umijs/max'; import defaultSettings from './defaultSettings'; import proxy from './proxy'; import routes from './routes'; const { REACT_APP_ENV = 'dev' } = process.env; const PUBLIC_PATH: string = '/'; export default defineConfig({ hash: true, publicPath: PUBLIC_PATH, routes, ignoreMomentLocale: true, proxy: proxy[REACT_APP_ENV as keyof typeof proxy], fastRefresh: true, model: {}, initialState: {}, title: 'Ant Design Pro', layout: { locale: false, ...defaultSettings, // ==================== 在这里添加多标签页配置 ==================== multiTabs: { // 这里修改为复数形式 enabled: true, // 启用多标签页 keepAlive: true, // 启用页面缓存 multiTabMode: 'chrome', // 标签页样式 fixedMultiTab: true, // 固定标签头 }, }, moment2dayjs: { preset: 'antd', plugins: ['duration'], }, locale: { default: 'zh-CN', antd: true, baseNavigator: true, }, antd: { appConfig: {}, configProvider: { theme: { cssVar: true, token: { fontFamily: 'AlibabaSans, sans-serif', }, }, }, }, request: {}, access: {}, headScripts: [ { src: join(PUBLIC_PATH, 'scripts/loading.js'), async: true }, ], presets: ['umi-presets-pro'], openAPI: [ { requestLibPath: "import { request } from '@umijs/max'", schemaPath: join(__dirname, 'oneapi.json'), mock: false, }, { requestLibPath: "import { request } from '@umijs/max'", schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', projectName: 'swagger', }, ], mock: { include: ['mock/**/*', 'src/pages/**/_mock.ts'], }, mako: {}, esbuildMinifyIIFE: true, requestRecord: {}, exportStatic: {}, history: { type: 'hash' }, }); import type { ProLayoutProps } from '@ant-design/pro-components'; /** * @name */ const Settings: ProLayoutProps & { pwa?: boolean; logo?: string; } = { navTheme: 'light', // 拂晓蓝 colorPrimary: '#1890ff', layout: 'mix', contentWidth: 'Fluid', fixedHeader: false, fixSiderbar: true, colorWeak: false, title: '框架', pwa: true, logo: './logo.svg', // 项目logo iconfontUrl: '', token: { // 参见ts声明,demo 见文档,通过token 修改样式 //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F }, // 侧边栏宽度 siderWidth: 220, }; export default Settings; /** * @name umi路由配置 * @description 只支持 path,component,routes,redirect,wrappers,name,icon配置 * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 * @param routes 配置路由,通常在需要为多个路径增加 layout 组件时使用。 * @param redirect 配置路由跳转 * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 * @param icon 配置路由图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User * @doc https://umijs.org/docs/guides/routes */ export default [ { path: '/user', layout: false, routes: [ { name: 'login', path: '/user/login', component: './user/login', }, ], }, { path: '/company', name: '组织管理', icon: 'smile', component: './company', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: '/permissions', name: '权限管理', icon: 'lock', access: 'hasRoute', // 移除冗余的 multiTab 配置 routes: [ { path: 'admin', name: '账户管理', component: './permissions/account', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: 'menu', name: '菜单管理', component: './permissions/menu', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: 'role', name: '角色管理', component: './permissions/role', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, ], }, { path: '/staff', name: '员工管理', icon: 'smile', component: './staff', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: '/', redirect: '/staff', }, { path: '*', layout: false, component: './404', }, ]; import { LinkOutlined, FullscreenOutlined, FullscreenExitOutlined, RedoOutlined, LoadingOutlined } from '@ant-design/icons'; import type { Settings as LayoutSettings } from '@ant-design/pro-components'; import { SettingDrawer } from '@ant-design/pro-components'; import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max'; import { history, Link, useLocation } from '@umijs/max'; import React, { useState, useEffect, useCallback } from 'react'; import { AvatarDropdown, AvatarName, Footer, Question, SelectLang, } from '@/components'; import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; import '@ant-design/v5-patch-for-react-19'; const isDev = process.env.NODE_ENV === 'development'; const loginPath = '/user/login'; // 全屏钩子保持不变 const useFullscreen = () => { const [isFullscreen, setIsFullscreen] = useState(false); const checkFullscreen = useCallback(() => { setIsFullscreen( !!(document as any).fullscreenElement || !!(document as any).webkitFullscreenElement || !!(document as any).mozFullScreenElement || !!(document as any).msFullscreenElement ); }, []); const toggleFullscreen = useCallback(() => { if (isFullscreen) { if ((document as any).exitFullscreen) (document as any).exitFullscreen(); else if ((document as any).webkitExitFullscreen) (document as any).webkitExitFullscreen(); else if ((document as any).mozCancelFullScreen) (document as any).mozCancelFullScreen(); else if ((document as any).msExitFullscreen) (document as any).msExitFullscreen(); } else { const docEl = document.documentElement; if ((docEl as any).requestFullscreen) (docEl as any).requestFullscreen(); else if ((docEl as any).webkitRequestFullscreen) (docEl as any).webkitRequestFullscreen(); else if ((docEl as any).mozRequestFullScreen) (docEl as any).mozRequestFullScreen(); else if ((docEl as any).msRequestFullscreen) (docEl as any).msRequestFullscreen(); } }, [isFullscreen]); useEffect(() => { const fullscreenChangeHandler = () => checkFullscreen(); document.addEventListener('fullscreenchange', fullscreenChangeHandler); document.addEventListener('webkitfullscreenchange', fullscreenChangeHandler); document.addEventListener('mozfullscreenchange', fullscreenChangeHandler); document.addEventListener('MSFullscreenChange', fullscreenChangeHandler); return () => { document.removeEventListener('fullscreenchange', fullscreenChangeHandler); document.removeEventListener('webkitfullscreenchange', fullscreenChangeHandler); document.removeEventListener('mozfullscreenchange', fullscreenChangeHandler); document.removeEventListener('MSFullscreenChange', fullscreenChangeHandler); }; }, [checkFullscreen]); return { isFullscreen, toggleFullscreen }; }; export async function getInitialState(): Promise<{ settings?: Partial<LayoutSettings>; currentUser?: API.CurrentUser; loading?: boolean; fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; }> { const fetchUserInfo = async () => { try { const msg = await queryCurrentUser({ skipErrorHandler: true, }); return msg.data; } catch (_error) { history.push(loginPath); } return undefined; }; const { location } = history; if ( ![loginPath, '/user/register', '/user/register-result'].includes( location.pathname, ) ) { const currentUser = await fetchUserInfo(); return { fetchUserInfo, currentUser, settings: defaultSettings as Partial<LayoutSettings>, }; } return { fetchUserInfo, settings: defaultSettings as Partial<LayoutSettings>, }; } export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState, }) => { const { isFullscreen, toggleFullscreen } = useFullscreen(); const [routeKey, setRouteKey] = useState(Date.now()); const [isRefreshing, setIsRefreshing] = useState(false); const location = useLocation(); // 刷新方法 const refreshPage = useCallback(() => { setIsRefreshing(true); setRouteKey(Date.now()); setTimeout(() => setIsRefreshing(false), 300); }, []); return { // 全局开启多标签功能(复数形式,与ProLayout属性一致) multiTabs: true, actionsRender: () => [ // 全屏按钮 isFullscreen ? ( <FullscreenExitOutlined key="exit-fullscreen" onClick={toggleFullscreen} title="退出全屏" style={{ cursor: 'pointer', marginRight: 8 }} /> ) : ( <FullscreenOutlined key="fullscreen" onClick={toggleFullscreen} title="全屏显示" style={{ cursor: 'pointer', marginRight: 8 }} /> ), // 刷新按钮 isRefreshing ? ( <LoadingOutlined key="refresh-loading" spin style={{ fontSize: '16px', cursor: 'pointer', marginRight: 8 }} title="刷新中..." onClick={refreshPage} /> ) : ( <RedoOutlined key="refresh" style={{ fontSize: '16px', cursor: 'pointer', marginRight: 8 }} title="刷新页面" onClick={refreshPage} /> ), ], avatarProps: { src: initialState?.currentUser?.avatar, title: <AvatarName />, render: (_, avatarChildren) => { return <AvatarDropdown>{avatarChildren}</AvatarDropdown>; }, }, footerRender: () => <Footer />, onPageChange: () => { const { location } = history; if (!initialState?.currentUser && location.pathname !== loginPath) { history.push(loginPath); } }, menuHeaderRender: undefined, childrenRender: (children) => { return ( <div key={routeKey}> {children} </div> ); }, ...initialState?.settings, }; }; export const request: RequestConfig = { ...errorConfig };怎么修复多标签页
最新发布
11-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoe_ya

如果你成功申请,可以打赏杯奶茶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值