JS 遞歸函數(shù)來遍歷菜單樹 2

在 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é)點(通過 filtertransform 返回 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é)點”的專用方法嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容