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