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