react学习笔记-7:菜单栏

抽象独立组件

从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;
        }
        
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

snipercai

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值