假设,我们要使用react + Antd(4.24.8)实现如下的一个侧边栏:
我们可以给出如下的代码和涉及的知识:
- 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组件创建完毕!