抽象独立组件
从Home.tsx中的layout布局源码中抽离menu组件为独立组件
layout原始代码如下
import React, { useState } from 'react';
import {
DesktopOutlined,
FileOutlined,
PieChartOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Breadcrumb, Layout, Menu, theme } from 'antd';
const { Header, Content, Footer, Sider } = Layout;
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('User', 'sub1', <UserOutlined />, [
getItem('Tom', '3'),
getItem('Bill', '4'),
getItem('Alex', '5'),
]),
getItem('Team', 'sub2', <TeamOutlined />, [getItem('Team 1', '6'), getItem('Team 2', '8')]),
getItem('Files', '9', <FileOutlined />),
];
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer },
} = theme.useToken();
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
<div style={{ height: 32, margin: 16, background: 'rgba(255, 255, 255, 0.2)' }} />
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items} />
</Sider>
<Layout className="site-layout">
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: '0 16px' }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>User</Breadcrumb.Item>
<Breadcrumb.Item>Bill</Breadcrumb.Item>
</Breadcrumb>
<div style={{ padding: 24, minHeight: 360, background: colorBgContainer }}>
Bill is a cat.
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2023 Created by Ant UED</Footer>
</Layout>
</Layout>
);
};
export default App;
在components中新建MainMenu文件夹,创建index.tsx文件。
1、优化menu组件中items定义赋值。
//原始代码
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const items: MenuItem[] = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('User', 'sub1', <UserOutlined />, [
getItem('Tom', '3'),
getItem('Bill', '4'),
getItem('Alex', '5'),
]),
getItem('Team', 'sub2', <TeamOutlined />, [getItem('Team 1', '6'), getItem('Team 2', '8')]),
getItem('Files', '9', <FileOutlined />),
];
//优化后代码
const items: MenuItem[] = [
{
label:'栏目 1',
key:'/page1',
icon:<PieChartOutlined />,
children:[]
},
{
label:'栏目 2',
key:'/page2',
icon:<DesktopOutlined />,
children:[]
},
{
label:'栏目 3',
key:'sub1',
icon:<UserOutlined />,
children:[
{
label:'栏目 301',
key:'/page3/page301',
},
{
label:'栏目 302',
key:'/page302',
}
]
},
{
label:'栏目 4',
key:'sub2',
icon:<TeamOutlined />,
children:[
{
label:'栏目 401',
key:'/page401',
},
{
label:'栏目 402',
key:'/about'
}
]
}
]
抽离后menu独立组件源码如下:
import { useState } from 'react';
import {
DesktopOutlined,
FileOutlined,
PieChartOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import { useNavigate } from 'react-router-dom';
type MenuItem = Required<MenuProps>['items'][number];
//等请求到数据之后,就可以跟items数组进行匹配
const items: MenuItem[] = [
{
label:'栏目 1',
key:'/page1',
icon:<PieChartOutlined />,
children:[]
},
{
label:'栏目 2',
key:'/page2',
icon:<DesktopOutlined />,
children:[]
},
{
label:'栏目 3',
key:'sub1',
icon:<UserOutlined />,
children:[
{
label:'栏目 301',
key:'/page3/page301',
},
{
label:'栏目 302',
key:'/page302',
}
]
},
{
label:'栏目 4',
key:'sub2',
icon:<TeamOutlined />,
children:[
{
label:'栏目 401',
key:'/page401',
},
{
label:'栏目 402',
key:'/about'
}
]
}
]
const MainMenu: React.FC = () => {
const navigateTo = useNavigate()
//定义menuClick事件
const menuClick = (e:{key:string}) => {
console.log("点击了菜单",e.key);
//点击跳转到对应的路由,编程式导航跳转,使用一个hook
navigateTo(e.key)
}
const [openKeys, setOpeKeys] = useState(['']);
const handleOpenChange = (keys:string[]) => {
//keys是一个数组,记录了当前哪个是展开的,用key来记录
console.log(keys);
//展开和回收某项菜单的时候执行这里的代码
//把数组修改成最后一项,可以让之前展开的都回收
setOpeKeys([keys[keys.length-1]]);
}
return (
<Menu
theme="dark"
defaultSelectedKeys={['/page1']}
mode="inline"
//菜单项的数据
items={items}
//点击菜单的事件
onClick={menuClick}
//某项菜单展开和回收的事件
onOpenChange={handleOpenChange}
//当前菜单展开项的key数组,可以在antd的menu组件说明查询
openKeys={openKeys}
/>
);
};
export default MainMenu;
修改Home.tsx引用MainMenu组件,修改后的代码如下:
import React, { useState } from 'react';
import { Breadcrumb, Layout, theme } from 'antd';
import { Outlet } from 'react-router-dom';
import MainMenu from '@/components/MainMenu';
const { Header, Content, Footer, Sider } = Layout;
const View: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer },
} = theme.useToken();
return (
<Layout style={{ minHeight: '100vh' }}>
{/* 左边侧边栏 */}
<Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
<div style={{ height: 32, margin: 16, background: 'rgba(255, 255, 255, 0.2)' }} />
<MainMenu />
</Sider>
{/* 右边内容 */}
<Layout className="site-layout">
{/* 右边头部 */}
<Header style={{ padding: 0, background: colorBgContainer }} />
{/* 右边内容 */}
<Content style={{ margin: '0 16px' }}>
{/* 面包蟹 */}
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>User</Breadcrumb.Item>
<Breadcrumb.Item>Bill</Breadcrumb.Item>
</Breadcrumb>
{/* 内容盒子 */}
<div style={{ padding: 24, minHeight: 360, background: colorBgContainer }}>
{/* 窗口部分 */}
<Outlet></Outlet>
</div>
</Content>
{/* 右边底部 */}
<Footer style={{ textAlign: 'center' }}>Ant Design ©2023 Created by Ant UED</Footer>
</Layout>
</Layout>
);
};
export default View;
路由修改
import React,{ lazy } from "react"
import Home from "../views/Home"
import { Navigate } from "react-router-dom"
const About = lazy(()=>import ("../views/About"))
const Page1 = lazy(()=>import ("../views/Page1"))
const Page2 = lazy(()=>import ("../views/Page2"))
const Page301 = lazy(()=>import ("../views/Page301"))
//懒加载的模式的组件写法,外面需要套一层loading的提示加载组件
//定义一个Loading函数
const withLoadingComponent = (comp:JSX.Element) =>{
return (
<React.Suspense fallback={<div>Loading...</div>}>
{comp}
</React.Suspense>
)
}
const routes = [
//嵌套路由 开始 -------
{
path:"/",
element:<Navigate to='/page1' />
},
{
path:"/",
element:<Home />,
children:[
{
path:"/page1",
element:withLoadingComponent(<Page1 />)
},
{
path:"/page2",
element:withLoadingComponent(<Page2 />)
},
{
path:"/page3/page301",
element:withLoadingComponent(<Page301 />)
}
]
},
//嵌套路由 结束 -------
{
path:"/about",
element:withLoadingComponent(<About />)
},
//访问其余路径的时候直接跳转到首页
{
path:"*",
element:<Navigate to='/page1' />
}
]
export default routes
打开二级菜单页时自动展开父菜单
import { useNavigate,useLocation } from 'react-router-dom';
//获得当前位置信息,包含key、pathname等信息
const currentRoute = useLocation();
console.log(currentRoute.pathname);
//拿到pathname跟items数组中每一项children的key对比,找到就反馈其上一级的key
//将这个key传给openKeys数组的元组作为初始值
let firstOpenkey:string = "";
//使用find进行对比
function findkey(obj:{key:string}){
return obj.key === currentRoute.pathname
}
//遍历
for(let i=0;i<items.length;i++){
if(items[i]!['children'] && items[i]!['children'].length>0 && items[i]!['children'].find(findkey)){
firstOpenkey = items[i]!.key;
break;
}
}