天天看點

基于react實作無限分級菜單分析&設計資料結構&算法體驗效果(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 收起