在 JavaScript 中,你可以編寫一個(gè)通用的遞歸函數(shù)來遍歷樹形結(jié)構(gòu)(如菜單),并支持對(duì)每個(gè)節(jié)點(diǎn)進(jìn)行修改、新增字段、過濾、刪除、替換等操作。下面是一個(gè)靈活的遞歸菜單處理方法,它接收一個(gè)菜單數(shù)組和一個(gè)配置對(duì)象,該配置對(duì)象可以定義你想要執(zhí)行的操作。
? 示例:通用遞歸菜單處理器
/**
* 遞歸處理菜單樹
* @param {Array} menu - 菜單數(shù)據(jù),數(shù)組形式,每個(gè)項(xiàng)可能包含 children 字段
* @param {Object} options - 配置選項(xiàng)
* @param {Function} [options.transform] - 修改/新增字段:(node, path) => newNode | undefined(返回新節(jié)點(diǎn)或 undefined 表示刪除)
* @param {Function} [options.filter] - 過濾條件:(node, path) => boolean(返回 false 則刪除該節(jié)點(diǎn)及其子樹)
* @returns {Array} 處理后的新菜單(不修改原數(shù)據(jù))
*/
function processMenu(menu, options = {}) {
const { transform, filter } = options;
function walk(nodes, path = []) {
if (!Array.isArray(nodes)) return [];
const result = [];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const currentPath = [...path, i];
// 如果有 filter 且返回 false,則跳過該節(jié)點(diǎn)(包括子樹)
if (filter && !filter(node, currentPath)) {
continue;
}
// 深拷貝節(jié)點(diǎn)(避免修改原始數(shù)據(jù))
let newNode = JSON.parse(JSON.stringify(node));
// 應(yīng)用 transform
if (transform) {
const transformed = transform(newNode, currentPath);
if (transformed === undefined) {
// 返回 undefined 表示刪除該節(jié)點(diǎn)
continue;
}
newNode = transformed;
}
// 遞歸處理 children
if (newNode.children && Array.isArray(newNode.children)) {
newNode.children = walk(newNode.children, [...currentPath, 'children']);
// 可選:如果 children 為空,是否刪除 children 字段?
// if (newNode.children.length === 0) delete newNode.children;
}
result.push(newNode);
}
return result;
}
return walk(menu);
}
?? 使用示例
假設(shè)原始菜單如下:
const menu = [
{
id: 1,
name: '首頁',
path: '/home'
},
{
id: 2,
name: '系統(tǒng)管理',
children: [
{ id: 3, name: '用戶管理', path: '/user' },
{ id: 4, name: '角色管理', path: '/role' }
]
},
{
id: 5,
name: '無效菜單',
disabled: true
}
];
1. 新增字段(比如加 label)
const newMenu = processMenu(menu, {
transform(node) {
node.label = node.name; // 新增字段
return node;
}
});
2. 修改字段
const newMenu = processMenu(menu, {
transform(node) {
if (node.path) {
node.path = node.path.toUpperCase(); // 轉(zhuǎn)大寫
}
return node;
}
});
3. 刪除某些節(jié)點(diǎn)(通過 filter 或 transform 返回 undefined)
- 使用
filter刪除禁用項(xiàng):
const newMenu = processMenu(menu, {
filter(node) {
return !node.disabled; // 過濾掉 disabled 為 true 的項(xiàng)
}
});
- 使用
transform刪除特定項(xiàng):
const newMenu = processMenu(menu, {
transform(node) {
if (node.id === 5) return undefined; // 刪除 id=5 的項(xiàng)
return node;
}
});
4. 組合操作:過濾 + 修改 + 新增
const newMenu = processMenu(menu, {
filter: (node) => !node.disabled,
transform: (node) => {
node.key = node.id; // 新增 key
node.title = node.name; // 改名
delete node.name; // 刪除原字段
return node;
}
});
?? 注意事項(xiàng)
- 本方法使用
JSON.parse(JSON.stringify(...))實(shí)現(xiàn)深拷貝,適用于純對(duì)象(不含函數(shù)、Date、RegExp 等)。如有復(fù)雜數(shù)據(jù),建議使用structuredClone(現(xiàn)代瀏覽器)或 lodash 的cloneDeep。 -
path參數(shù)可用于調(diào)試或根據(jù)路徑做特殊處理(如只修改某一層)。 - 所有操作不會(huì)修改原始菜單,而是返回新數(shù)組。
如果你有更具體的需求(比如只改某一層、按 ID 查找后修改等),也可以擴(kuò)展這個(gè)函數(shù)。需要我?guī)湍銓懸粋€(gè)“根據(jù) ID 修改節(jié)點(diǎn)”的專用方法嗎?