1. 程式人生 > >基於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元件)

demo可以點選體驗展開 or 收起