React 动态菜单设计与实现

需求

  • 动态显示隐藏菜单
  • 动态修改菜单层级、菜单标题
  • 不同权限的人菜单不同
  • 隐藏的菜单不能通过 url 访问

目标菜单样例如下:


目标菜单样例.png

设计与实现

首先前后端需要约定有哪些用于菜单的页面,并约定好页面的key作为前后端的连接点。前端建立所有页面key与跳转链接、组件的映射,后端返回实际页面key和菜单数据。我们以一个简单的例子作为演示,假设系统有两个页面用于菜单['hooks', 'vue']

页面key与跳转链接、组件映射

const routes = {
  hooks: { path: `${rootUrl}/hooks`, component: Hooks },
  vue: { path: `${rootUrl}/vue`, component: Vue },
};

后端生成菜单数据

我们采用数组来存储菜单数据,menus对象中的page属性对应约定好的页面key,title属性是菜单标题,children属性对应子菜单。菜单数据示例如下:

const menus = [
  {
    title: "react文档",
    children: [
      {
        title: "api介绍",
        children: [
          {
            page: "hooks",
            title: "hooks使用",
          },
        ],
      },
    ],
  },
  {
    title: "vue概述",
    page: "vue",
  },
];

渲染菜单

根据后端返回的菜单数据,我们要渲染菜单。最终我们要渲染的菜单如下:


  
    
      
        hooks使用
      
    
  
  
    vue概述
  

根据观察我们发现可以把数组中的每一项可以看作是一个树,比如react文档这个树有一个子树api介绍api介绍有子树hooks使用。要生成树对应的菜单项,只需要先获得所有子树的菜单项,因此可以采用先序遍历实现。实现如下:

class SiderMenu extends React.PureComponent {
  renderMenuItem = (menu) => {
    if (menu.children && menu.children.length > 0) {
      const renderChildrenItems = [];
      for (const child of menu.children) {
        renderChildrenItems.push(this.renderMenuItem(child));
      }
      return (
        
          {renderChildrenItems}
        
      );
    } else {
      return (
        
          {menu.title}
        
      );
    }
  };
  render() {
    const { menus } = this.props;
    return (
      
        {menus.map((menu) => this.renderMenuItem(menu))}
      
    );
  }
}

如果children为空则返回{menu.title};如果children不为空则递归处理所有子树,并返回。注意由于key为uuid生成的,每次渲染都会生成新组件。所以此处SiderMenu设计为PureComponent,当menus变化才会重新渲染。

生成返回页面路由

如果后端返回的数据中没有某个页面,则通过地址栏中输入该页面url,不应该进入该页面。我们要得到后端返回的页面,可以采用树的先根遍历,所有叶子节点是后端返回的页面,如果有子节点遍历所有子节点。获取后端实际返回的页面代码如下:

const getValidPages = (menus) => {
    const pages = [];
    const visitMenu = (menu) => {
      if (menu.children) {
        for (const child of menu.children) {
          visitMenu(child);
        }
      } else {
        pages.push(menu.page);
      }
    };
    for (const menu of menus) {
      visitMenu(menu);
    }
    return pages;
  };

根据后端实际返回的页面生成路由:

    
      {Object.keys(filterRoutes).map((page) => {
        return (
          
        );
      })}
    

总结

本文提出了一种较为灵活的动态菜单生成策略,页面key作为前后端唯一联系点,难点在于根据业务场景抽象出数据结构:树,并根据需求采用合理的递归算法操作树结构。

完整代码:https://github.com/compus135/web-examples/tree/master/src/react/components/compflex/DynamicRouter

你可能感兴趣的:(React 动态菜单设计与实现)