React+Antd 实现侧边栏sideBar

2,431 阅读4分钟

假设,我们要使用react + Antd(4.24.8)实现如下的一个侧边栏:

image.png

我们可以给出如下的代码和涉及的知识:

  • useEffect, useState, useCallback,useHistory的使用
  • axios简单示例
  • 前后端路由图标配置
  • 配合Antd,将数据源处理为Antd可以利用的格式
  • antd--Menu默认属性的使用

贴出代码,并对其具体介绍:

import React, { useEffect, useState, useCallback } from 'react'
import { Layout, Menu } from 'antd'
import './SideMenu.css'
import axios from 'axios'
import { useHistory } from 'react-router-dom'
import iconList from '../../iconMap'

const { Sider } = Layout

export default function SideMenu(props) {
  const [menuList, setMenuList] = useState([])
  const history = useHistory()
  const mapMenusToStand = useCallback(menus => {
    return menus.map(menu => {
      if (menu.children) {
        if (menu.children.length) menu.children = mapMenusToStand(menu.children)
        else menu.children = null
      }
      menu.label = menu.title
      menu.rightId && (menu.rightid = menu.rightId)
      delete menu.rightId
      menu.icon = iconList[menu.key]
      return menu.pagepermisson && menu
    })
  }, [])

  useEffect(() => {
    axios.get('http://localhost:8000/rights?_embed=children').then(res => {
      console.log(res.data)
      const temp = mapMenusToStand(res.data)
      console.log(temp)
      setMenuList(temp)
    })
  }, [mapMenusToStand])

  // 第一次是重定向过来的,此时路径为'/',
  // 或者将defaultSelectedKeys-->SelectedKeys,这样组件为受控组件,值会跟着变
  const selectKeys = [history.location.pathname === '/' ? '/home' : history.location.pathname]
  const openKeys = ['/' + history.location.pathname.split('/')[1]]
  return (
    <Sider trigger={null} collapsible collapsed={props.collapsed} width={250}>
      <div className="siderBox">
        <div className="logo">全球新闻发布管理系统</div>
        <Menu
          theme="dark"
          mode="inline"
          defaultSelectedKeys={selectKeys}
          defaultOpenKeys={openKeys}
          items={menuList}
          className="siderMenu"
          onClick={item => {
            history.push(item.key)
          }}
        />
      </div>
    </Sider>
  )
}

首先从antd中引入Layout布局组件,并解构出Sider组件,设置基本属性,然后再从antd中引入Menu组件,这里对几个重点属性进行解释(defaultSelectedKeys、defaultOpenKeys、items、Onclick)

两个default属性设置了默认选中和默认打开功能,动态设置这两个属性可以做到刷新后侧边栏仍然保持高亮显示,根据menu菜单的规定,每一个menuItem的key值我们都设置为了路由地址:

//每一个item项需要传入如下格式的对象
{
    key:‘home’,
    label:‘首页’,
    icon:<antdIcon/>
    children:[{
        key,label,icon
    }]
}
//所以可以使用如下方式获取动态设置两个default属性
// 第一次是重定向过来的,此时路径为'/',
// 或者将defaultSelectedKeys-->SelectedKeys,这样组件为受控组件,值会跟着变
const history = useHistory()
const selectKeys = [history.location.pathname === '/' ? '/home' : history.location.pathname]
const openKeys = ['/' + history.location.pathname.split('/')[1]]
// 接下来为每一个Item绑定点击事件,执行编程式导航跳转就好啦
onClick={item => {
    history.push(item.key)//得益于前方良好的设计,key就是路由地址
}}

接下来最重要的就是设置menu组件的数据源,在antd4.20版本之后menu组件的用法发生了改变,不再自行拼接JSX,而是直接传入一个对象数组,组件根据属性进行渲染,这样做如果前后端有良好的约定,直接将请求的数据同步到state设置数据源即可,但有时候后端给的数据可能不符合antd组件的规定,这时候我们就需要自己写一个数据转换方法将请求的数据转化为antd需要的格式

我们需要先在副作用函数中请求数据:

const [menuList, setMenuList] = useState([])

useEffect(() => {
    axios.get('http://localhost:8000/rights?_embed=children').then(res => {
      console.log(res.data)
      const temp = mapMenusToStand(res.data)
      console.log(temp)
      setMenuList(temp)
    })
  }, [mapMenusToStand])
  
  const mapMenusToStand = useCallback(menus => {
    return menus.map(menu => {
      if (menu.children) {
        if (menu.children.length) menu.children = mapMenusToStand(menu.children)
        else menu.children = null
      }
      menu.label = menu.title
      menu.rightId && (menu.rightid = menu.rightId)
      delete menu.rightId
      menu.icon = iconList[menu.key]
      return menu.pagepermisson && menu
    })
  }, [])

第一个useEffect函数中我们利用axios发送请求得到了返回数据,log一下发现属性并不符合要求,需要调用mapMenusToStand方法进行改造,同时根据需求,我们应当设置监听数组为空以此模拟componentDidMount生命周期钩子

但是hooks写法中,mapMenusToStand是一个副作用的依赖项,每次改变都需要重新执行副作用,但这不符合我们的要求,我们只希望在组件渲染时发送一次请求,因此我们需要使用useCallback将函数改变为记忆函数

接下来是核心函数mapMenusToStand的解读:首先使用数组的map方法对每一条数据进行映射

如果有children属性,则需要进行递归调用,当length>0,传入children对象数组并将返回值设置给children属性,如果length为0则需要设置为null,这里是因为返回的数据中有children:[]的情况,这样antdMenu组件会将其当成有子Item的模式进行渲染

如果没有children属性,则需要添加一个label属性,并将rightId改为rightid属性,因为react对自定义属性需要全小写命名

随后设置menu的icon属性,这里是前端维护一个图标映射表,根据路由选择对应的icon

import {
  HomeOutlined,
  UserOutlined,
  UsergroupAddOutlined,
  ThunderboltOutlined,
  ProfileOutlined,
  PaperClipOutlined,
  PlaySquareOutlined
} from '@ant-design/icons'

const iconList = {
  '/home': <HomeOutlined />,
  '/user-manage': <UserOutlined />,
  '/user-manage/list': <UsergroupAddOutlined />,
  '/right-manage': <ThunderboltOutlined />,
  '/right-manage/role/list': <ThunderboltOutlined />,
  '/right-manage/right/list': <ThunderboltOutlined />,
  '/news-manage': <ProfileOutlined />,
  '/news-manage/add': <ProfileOutlined />,
  '/news-manage/draft': <ProfileOutlined />,
  '/news-manage/category': <ProfileOutlined />,
  '/audit-manage': <PaperClipOutlined />,
  '/audit-manage/audit': <PaperClipOutlined />,
  '/audit-manage/list': <PaperClipOutlined />,
  '/publish-manage': <PlaySquareOutlined />,
  '/publish-manage/unpublished': <PlaySquareOutlined />,
  '/publish-manage/published': <PlaySquareOutlined />,
  '/publish-manage/sunset': <PlaySquareOutlined />
}

export default iconList

最后设置return menu.pagepermisson && menu,因为后端返回的路由表中,有些不是侧边栏需要显示的路由,使用pagepermission进行了标识,我们需要进行过滤

至此sideMenu组件创建完毕!