zoukankan      html  css  js  c++  java
  • 基于react实现无限分级菜单

    在开发CMS(内容管理系统)系统时,一般都会用到一个侧边栏或者顶部的二级或者三级菜单,当点击或者鼠标悬浮时,菜单能够随之展开或收起。

    本文纯粹为了练习一下react,因此我会在react环境下实现这么一个小组件:它假设了菜单数据来自于网络请求,并且仅实现无限分级菜单的核心功能(父子关系,展开与收起),至于样式则不是关注的重点。

    分析&设计

    既然要实现一个动态生成的无限分级菜单,最简单的切入思路就是分析一个静态的菜单:其DOM树是怎样构成的?

    下面是一个典型的HTML结构:

    <ul>
        <li>
              <h1>菜单1</h1>
              <ul>
                      <li>
                              <h1>菜单1-1</h1>
                              <ul></ul>
                      </li>
              </ul>
        </li>
        <li>
              <h1>菜单2</h1>
              <ul>
                      <li>
                              <h1>菜单2-1</h1>
                              <ul>
                                         <li>
                                               <h1>菜单2-1-1</h1>
                                               <ul></ul>
                                        </li>
                              </ul>
                      </li>
              </ul>
        </li>
    </ul>

    这是一个3级菜单,我们需要仔细观察它的构成规律:

    • 首先<ul>代表了一个菜单列表,它里面包含了若干<li>代表其中1个菜单项
    • 每个<li>菜单项至少包含自己的标题,其次也应包含它下面的子菜单列表(也就是另一个<ul>)。

    上述2个规则基本就是分级菜单的核心构思了,通过一个比较白话的描述可以这么理解:

    要画一个菜单列表,那么就要去画它的每一个菜单项。

    要画一个菜单项,那么就要画出标题,然后去画它的子菜单列表。

    如果你反复的读上面的话,你可以感受到一种『递归的味道』。

    没错,要根据菜单数据动态的画出一个无限分级的菜单是要用递归算法的。有意思的是,递归算法本身是深度优先的,而这恰好满足我们从上至下从外至内顺序追加HTML标签,从而最终构成完整的DOM树的编程思路。

    数据结构&算法

    程序设计=数据结构+算法。

    因此,先用一个合理的数据结构来描述之前我们的构思,之后基于数据结构进行算法的实现,最终形成程序,这是我们正确的编程思路。

    通常,菜单结构是服务端拼装的,它描述了菜单的父子和顺序关系,并且每个菜单拥有自己的唯一ID,这些都体现在我的render()方法中:

        render() {
            let data = [
                {
                    menuId: 1,
                    name: '员工管理',
                    children: [
                        {
                            menuId: 3,
                            name: '添加员工',
                            children: []
                        },
                        {
                            menuId: 4,
                            name: '删除员工',
                            children: [
                                {
                                    menuId: 6,
                                    name: '按姓名删除',
                                    children: []
                                },
                                {
                                    menuId: 7,
                                    name: '按工号删除',
                                    children: []
                                }
                            ]
                        }
                    ],
                },
                {
                    menuId: 2,
                    name: '工资管理',
                    children: [
                        {
                            menuId: 5,
                            name: '修改工资',
                            children: []
                        }
                    ],
                },
            ];
    
            return (
                <div>
                    {this.generateMenu(data)}
                </div>
            );
        }

    最外层是一个菜单列表(Array),每个菜单项(Object)里有自己的标题,唯一ID,以及子菜单列表(Array)。

    在render()方法里调用了我实现的递归算法generateMenu(data),根据上述数据结构和原理递归的生成了DOM树:

        /**
         * 递归生成菜单
         * @param menuObj
         * @returns {Array}
         */
        generateMenu(menuObj) {
            let vdom = [];
    
            if (menuObj instanceof Array) {
                let list = [];
                for (var item of menuObj) {
                    list.push(this.generateMenu(item));
                }
                vdom.push(
                    <ul key="single">
                        {list}
                    </ul>
                );
            } else {
                vdom.push(
                    <li key={menuObj.menuId}>
                        <h1 onClick={this.onMenuClicked}>
                            {menuObj.name}
                        </h1>
                        {this.generateMenu(menuObj.children)}
                    </li>
                );
            }
            return vdom;
        }
    • 第1个分支判断:如果当前对象是菜单列表(Array类型),那么应生成1个新的<ul>元素,并且递归画出每一个菜单项(Object类型)。
    • 第2个分支判断:如果当前对象是菜单项(Object类型),那么应生成1个新的<li>元素,填充1个<h1>作为标题,其次递归画出它的子菜单列表。

    最后,为了实现点击鼠标展开和收起菜单,我为每一个<h1>标签注册了onClick事件,当它们被点击时找到<h1>的兄弟<ul>元素(利用jquery搞定),修改其CSS display属性即可实现展现和隐藏其子菜单列表的效果了。

    体验效果(react组件)

    查看代码:https://github.com/owenliang/react/tree/master/component/MenuPage

    查看demo:http://yuerblog.cc/wp-content/uploads/2016/11/output/#/menu-page

    demo可以点击体验展开 or 收起

     

  • 相关阅读:
    通过HttpListener实现简单的Http服务
    WCF心跳判断服务端及客户端是否掉线并实现重连接
    NHibernate初学六之关联多对多关系
    NHibernate初学五之关联一对多关系
    EXTJS 4.2 资料 跨域的问题
    EXTJS 4.2 资料 控件之Grid 那些事
    EXTJS 3.0 资料 控件之 GridPanel属性与方法大全
    EXTJS 3.0 资料 控件之 Toolbar 两行的用法
    EXTJS 3.0 资料 控件之 combo 用法
    EXTJS 4.2 资料 控件之 Store 用法
  • 原文地址:https://www.cnblogs.com/qq120848369/p/6074927.html
Copyright © 2011-2022 走看看