在開發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 收起