前端JS中常用的处理树型结构的常用工具函数

  • 使用了lodash的深拷贝方法
import { cloneDeep } from 'lodash-es'

/**
 * 遍历 tree
 * @param {object[]} tree
 * @param {function} cb - 回调函数
 * @param {string} children - 子节点 字段名
 * @param {string} mode - 遍历模式,DFS:深度优先遍历 BFS:广度优先遍历
 * @return {void} Do not return anything
 */
export function treeForEach(tree: any[], cb: (item: any) => unknown, children = 'children', mode = 'DFS'): void {
  if (!Array.isArray(tree)) {
    throw new TypeError('tree is not an array')
  }
  if (typeof children !== 'string') {
    throw new TypeError('children is not a string')
  }
  if (children === '') {
    throw new Error('children is not a valid string')
  }

  // 深度优先遍历 depth first search
  function DFS(treeData: any[]) {
    // eslint-disable-next-line
    for (const item of treeData) {
      cb(item)

      if (Array.isArray(item[children])) {
        DFS(item[children])
      }
    }
  }

  // 广度优先遍历 breadth first search
  function BFS(treeData: any[]) {
    const queen = treeData

    while (queen.length > 0) {
      const item = queen.shift()

      cb(item)

      if (Array.isArray(item[children])) {
        queen.push(...item[children])
      }
    }
  }
  if (mode === 'BFS') {
    BFS(tree)
  } else {
    DFS(tree)
  }
}

/**
 * tree 转 数组
 * @param {object[]} tree
 * @param {string} children - 子节点 字段名
 * @param {string} mode - 遍历模式,DFS:深度优先遍历 BFS:广度优先遍历
 * @return {array}
 */
export function treeToList(tree: Array<any>, children = 'children', mode = 'DFS'): Array<any> {
  if (!Array.isArray(tree)) {
    throw new TypeError('tree is not an array')
  }
  if (typeof children !== 'string') {
    throw new TypeError('children is not a string')
  }
  if (children === '') {
    throw new Error('children is not a valid string')
  }

  tree = cloneDeep(tree)

  const list: any = []

  treeForEach(tree, (item : any) => {
    list.push(item)
  }, children, mode)

  list.forEach((item: any) => {
    delete item[children] // 会改变 原数据
  })

  return list
}

/**
 * 数组 转 tree
 * @param {object[]} list
 * @param {object} options
 * @param {string|number|null|undefined} options.rootID - 根节点ID
 * @param {string|number} options.id - 唯一标识 字段名
 * @param {string|number} options.pid - 父节点ID 字段名
 * @param {string} options.children - 子节点 字段名
 * @return {array}
 */

interface Options {
  rootID?: string,
  id?: string,
  pid?: string,
  children?: string
}

export function listToTree(list: any[], options:Options): Array<any> {
  const {
    rootID = null, // 根节点ID,pid === rootID 即为 一级节点
    id = 'id', // 唯一标识
    pid = 'pid', // 父节点ID 字段
    children = 'children' // 子节点 字段
  } = options || {}

  if (!Array.isArray(list)) {
    throw new TypeError('list is not an array')
  }
  if (typeof children !== 'string') {
    throw new TypeError('children is not a string')
  }
  if (children === '') {
    throw new Error('children is not a valid string')
  }

  list = cloneDeep(list)

  const tree: any = []
  const map = list.reduce((res, item) => {
    res.set(item[id], item)

    return res
  }, new Map())

  Array.from(map.keys()).forEach(key => {
    const node = map.get(key)

    if (node[pid] === rootID) { // 一级节点,直接添加到 tree
      tree.push(node)
    } else { // 非一级节点,查找父级,并添加到父级 children 中
      const pNode = map.get(node[pid])

      if (Array.isArray(pNode[children])) {
        pNode[children].push(node)
      } else {
        pNode[children] = [node]
      }
    }
  })

  return tree
}
posted @   skylei  阅读(1509)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示