谈谈菜单数据结构的渲染

前言

菜单,常见于中台系统中,表现形式如:在网页的侧边栏中的导航菜单,今天,我们不说如何实现一个手风琴的菜单,我们就来谈谈它的渲染,
它的渲染流程一般如下:

  1. 从服务器中获取菜单数据结构
  2. 服务器中的菜单数据 通过与本地的菜单,进行组合,得到一个新得菜单数据结构,带有component
  3. 渲染 有component 得菜单数据结构

数据结构

菜单的数据结构,一般如下:

来自服务器端得数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export const fakeServiceMenu: any = [
{ name: "首页", path: "home" },
{
name: "系统设置",
path: "system-management",
children: [
{
name: "用户管理",
path: "user",
children: [
{
path: "account",
name: "账户管理",
},
{
path: "role",
name: "角色管理",
},
],
},
],
},
...
];

本地的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 本地的数据
const localMenu: any = [
{
path: "home",
name: "首页",
component: Home,
},
{
path: "system-management",
name: "系统设置",
children: [
{
path: "user",
name: "用户管理",
children: [
{
path: "account",
name: "账户管理",
component: Account,
},
{
path: "role",
name: "角色管理",
component: Role,
},
],
},
],
},
{
path: "changke-management",
name: "畅客管理",
children: [
{
path: "transaction",
name: "交易管理",
children: [
{
path: "transaction-statistic",
name: "数据统计",
component: TransactionStatisticHome,
},
],
},
{
path: "device",
name: "机具管理",
children: [
{
path: "device-statistic",
name: "数据统计",
component: DeviceStatisticHome,
},
],
},
],
},
];

可以看出,它是一个树状的结构,我们可以通过迭代去遍历它,但如何组合呢,最后得到我们类似与本地localMenu的
数据呢?不难看出,我们只要过滤LocalMenu就行了。但我们可以换一种方式来组合我们需要的数据,步骤如下:

  • 先通过服务器端的出现的key 查找对应的component
  • 遍历 服务器端的menu, 填充 component 即可

实现

getComponentByPath()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

function getComponentByPath(localMenu: any,path: string,isReturnObject?: boolean) {
let component = null;
for (let i = 0; i < localMenu.length; i++) {
component = getComponent(localMenu[i], path, isReturnObject);
if (component) {
return component;
}
}
return component;
}
// 递归函数
function getComponent(item: any, path: string, isReturnObject?: boolean) {
/*
如果查找到,并且 children为空,返回 item.component,
也就是递归结束的条件
*/
if (item.path === path && !item.children) {
if (isReturnObject) {
return item;
}
return item.component;
}
/*
如果有children ,我们就遍历遍历children,
然后继续递归调用,如果找到,component ,我们就直接返回,如果没有找到那就是
undefined
*/
if (item.children) {
let component = undefined;
for (let i = 0; i < item.children.length; i++) {
let component: any = getComponent(item.children[i], path, isReturnObject);
if (component) {
return component;
}
}
return component;
} else {
// 不符合,返回undefined
return undefined;
}
}

组合获取带有component的menuData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function filterMenuTree(serverMenu: any, localMenu: any) {

// 迭代 serverMenu
const composeComponent = (item: any) => {
// 如果没有children 加上 component 属性
if (!item.children) {
return { ...item, component: getComponentByPath(localMenu, item.path) };
} else {
// 否则 遍历 children 下面的元素
return {
...item,
children: item.children.map((items: any) => {
return composeComponent(items);
}),
};
}
};

return serverMenu.map((item: any) => composeComponent(item));
}

最后 渲染我们的filterMenus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from "react";
import { LayoutMenuBreadcrumb } from "antd";
import { UserOutlined } from "@ant-design/icons";
import { fakeServiceMenu, localMenu } from "../configs/route";
import { filterMenuTree } from "utils/tree";
const { SubMenu } = Menu;

interface MenuBarProps {
  onClick(param?: any) => void;
  defaultSelectedKeys?: string[] | undefined;
}

function MenuBar({ onClick, defaultSelectedKeys }: MenuBarProps) {

// 假的服务器端数据...
  const filterMenu = filterMenuTree(fakeServiceMenu, localMenu);

  const helper = (item: any) => {
// 递归停止条件
    if (!item.children) {
      return <Menu.Item key={item.path}>{item.name}</Menu.Item>;
    } else {
      return (
        <SubMenu key={item.path} icon={<UserOutlined />} title={item.name}>
          {item.children.map((items: any) => {
            return helper(items);
          })}
        </SubMenu>
      );
    }
  };

  const renderMenuItem = () => filterMenu.map((item: any) => helper(item));

  return (
    <Menu
      mode="inline"
      theme="dark"
      defaultSelectedKeys={defaultSelectedKeys}
      style={{ height: "100%", borderRight: 0 }}
      onClick={onClick}
    >
      {renderMenuItem()}
    </Menu>
  );
}

export default MenuBar;

小结

由于近期刷些算法题,才不至于对递归 晕头转向,递归对此数据类型简直是利器,但也要考虑调用栈溢出的问题,轻度使用便好


谈谈菜单数据结构的渲染
http://example.com/2020/05/11/旧的/谈谈菜单数据结构的渲染/
作者
chen heng cheng
发布于
2020年5月11日
许可协议