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",
  },
];

渲染菜单

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

<Menu>
  <Menu.SubMenu title="react文档">
    <Menu.SubMenu title="api介绍">
      <Menu.Item key={uuid()}>
        <Link>hooks使用</Link>
      </Menu.Item>
    </Menu.SubMenu>
  </Menu.SubMenu>
  <Menu.Item key={uuid()}>
    <Link>vue概述</Link>
  </Menu.Item>
</Menu>

根据观察我们发现可以把数组中的每一项可以看作是一个树,比如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 (
        <Menu.SubMenu key={uuid()} title={menu.title}>
          {renderChildrenItems}
        </Menu.SubMenu>
      );
    } else {
      return (
        <Menu.Item key={uuid()}>
          <Link to={routes[menu.page].path}>{menu.title}</Link>
        </Menu.Item>
      );
    }
  };
  render() {
    const { menus } = this.props;
    return (
      <Menu mode="inline" defaultSelectedKeys={["react"]}>
        {menus.map((menu) => this.renderMenuItem(menu))}
      </Menu>
    );
  }
}

如果children为空则返回<Menu.Item><Link >{menu.title}</Link></Menu.Item>;如果children不为空则递归处理所有子树,并返回<Menu.SubMenu{renderChildrenItems}</Menu.SubMenu>。注意由于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;
  };

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

    <Switch>
      {Object.keys(filterRoutes).map((page) => {
        return (
          <Route
            key={page}
            path={routes[page].path}
            component={routes[page].component}
          />
        );
      })}
    </Switch>

总结

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342