在 JavaScript 中,你可以編寫一個通用的遞歸函數(shù)來遍歷樹形結(jié)構(gòu)(如菜單),并支持對每個節(jié)點進行修改、新增字段、過濾、刪除、替換等操作。下面是一個靈活的遞歸菜單處理方法,它接收一個菜單數(shù)組和一個配置對象,該配置對象可以定義你想要執(zhí)行的操作。
? 示例:通用遞歸菜單處理器
/**
* 遞歸處理菜單樹
* @param {Array} menu - 菜單數(shù)據(jù),數(shù)組形式,每個項可能包含 children 字段
* @param {Object} options - 配置選項
* @param {Function} [options.transform] - 修改/新增字段:(node, path) => newNode | undefined(返回新節(jié)點或 undefined 表示刪除)
* @param {Function} [options.filter] - 過濾條件:(node, path) => boolean(返回 false 則刪除該節(jié)點及其子樹)
* @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é)點(包括子樹)
if (filter && !filter(node, currentPath)) {
continue;
}
// 深拷貝節(jié)點(避免修改原始數(shù)據(jù))
let newNode = JSON.parse(JSON.stringify(node));
// 應用 transform
if (transform) {
const transformed = transform(newNode, currentPath);
if (transformed === undefined) {
// 返回 undefined 表示刪除該節(jié)點
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é)點(通過 filter 或 transform 返回 undefined)
- 使用
filter刪除禁用項:
const newMenu = processMenu(menu, {
filter(node) {
return !node.disabled; // 過濾掉 disabled 為 true 的項
}
});
- 使用
transform刪除特定項:
const newMenu = processMenu(menu, {
transform(node) {
if (node.id === 5) return undefined; // 刪除 id=5 的項
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;
}
});
?? 注意事項
- 本方法使用
JSON.parse(JSON.stringify(...))實現(xiàn)深拷貝,適用于純對象(不含函數(shù)、Date、RegExp 等)。如有復雜數(shù)據(jù),建議使用structuredClone(現(xiàn)代瀏覽器)或 lodash 的cloneDeep。 -
path參數(shù)可用于調(diào)試或根據(jù)路徑做特殊處理(如只修改某一層)。 - 所有操作不會修改原始菜單,而是返回新數(shù)組。
如果你有更具體的需求(比如只改某一層、按 ID 查找后修改等),也可以擴展這個函數(shù)。需要我?guī)湍銓懸粋€“根據(jù) ID 修改節(jié)點”的專用方法嗎?