js,关于 tree 数据结构的递归算法工具。

javascript tree

背景:前端日常业务开发的工作中,关于树状数据结构的增删改查肯定避免不了, 这里整理下日常通用的工具函数,方便下次快速改造。

类型、使用数据示例

//以下函数示例的类型合数据 参考这里定义。
type IList = {
  id: number;
  pid: number | null;
  value: string;
};
type ITree = {
  children?: ITree[];
} & IList;

let list: IList[] = [
  { id: 1, pid: null, value: "Item 1" },
  { id: 2, pid: 1, value: "Item 1-1" },
  { id: 3, pid: 1, value: "Item 1-2" },
  { id: 4, pid: 2, value: "Item 1-1-1" },
  { id: 5, pid: null, value: "Item 2" },
];

let tree: ITree[] = [
  {
    id: 1,
    pid: null,
    value: "Item 1",
    children: [
      {
        id: 2,
        pid: 1,
        value: "Item 1-1",
        children: [{ id: 4, pid: 2, value: "Item 1-1-1" }],
      },
      { id: 3, pid: 1, value: "Item 1-2" },
    ],
  },
  { id: 5, pid: null, value: "Item 2" },
];

线性列表转树结构

/**
 * 匹配等于父节点的层,增加children,递归匹配查询
 * @param oldData 线性列表数据源
 * @param id 唯一标识
 * @param newData 转完返回的树结构
 * @returns
 */
const listToTree = (
  oldData: IList[],
  id: number | null,
  newData: ITree[] = []
) => {
  oldData.forEach((item) => {
    if (item.pid === id) {
      newData.push(item);
    }
  });
  newData.forEach((i) => {
    i.children = [];
    listToTree(oldData, i.id, i.children);
    if (i.children.length === 0) {
      delete i.children;
    }
  });
  return newData;
};

console.log(listToTree(list, null));

树结构转线性列表

/**
 * 递归,把每一个节点push到新数组
 * @param treeData tree数据源
 * @param result 线性列表
 * @returns
 */
const flattenTree = (treeData: ITree[], result: IList[] = []) => {
  treeData.forEach((node) => {
    result.push(node);
    if (node.children && node.children?.length > 0) {
      flattenTree(node.children, result);
      delete node.children;
    }
  });

  return result;
};

console.log(flattenTree(tree));

树:删除一个节点

/**
 * 删除树结构对应条件的某一节点  (引用删除)
 * @param treeData 数据源
 * @param id 唯一标识
 * @returns
 */
const removeTreeItem = (treeData: ITree[], id: number) => {
  if (!treeData || !treeData.length) {
    return;
  }
  for (let i = 0; i < treeData.length; i++) {
    let node = treeData[i];
    if (node.id === id) {
      treeData.splice(i, 1);
      break;
    }
    if (node.children && node.children?.length > 0) {
      removeTreeItem(node.children, id);
    }
  }
  return treeData;
};
console.log(JSON.stringify(removeTreeItem(tree, 2)));

树:批量过滤

/**
 * 批量过滤出树结构满足对应字段条件的节点 (从最顶层过滤,有缺陷)
 * @param {Array}  treeData 要查找的源数组
 * @param {functiuon} ruleVal   匹配的条件函数
 */
const filterTree = (treeData: ITree[], ruleVal: (item: ITree) => boolean) => {
  var newData = treeData.filter(ruleVal);
  newData.forEach(
    (item) =>
      item.children && (item.children = filterTree(item.children, ruleVal))
  );
  return newData;
};

console.log(
  JSON.stringify(
    filterTree(tree, (item: ITree) => item.value.indexOf("2") > -1)
  )
);

树:查询一个节点

/**
 *根据要查找的key返回这一项 (递归查找,引用查找)
 * @param {Array}  treeData 要查找的源数组
 * @param {string} id   唯一标识
 * @param {string} key   要查找的键名
 */
const findTreeItem = (
  treeData: ITree[],
  id: number,
  key: keyof ITree = "id"
) => {
  for (const item of treeData) {
    if (item[key] === id) return item;
    if (item.children && item.children.length > 0) {
      let forFindRet: any = findTreeItem(item.children, id, key);
      if (forFindRet) return forFindRet;
    }
  }
};

console.log(JSON.stringify(findTreeItem(tree, 2)));

树:增加一个节点

/**
 * 添加一个节点, (没有考虑节点的关联关系)
 * @param {Array}  treeData 数据源
 * @param {number} id  唯一标识
 * @param {ITree} newObj  新数据项
 */

const addTreeItem = (treeData: ITree[], id: number | null, newObj: ITree) => {
  if (!treeData || !treeData.length) {
    return;
  }
  if (id === null) {
    treeData.push(newObj);
    return treeData;
  }
  for (let i = 0; i < treeData.length; i++) {
    let node = treeData[i];
    if (node.id === id) {
      node.children ? node.children.push(newObj) : (node.children = [newObj]);
      break;
    }
    if (node.children && node.children?.length > 0) {
      addTreeItem(node.children, id, newObj);
    }
  }
  return treeData;
};
console.log(
  JSON.stringify(addTreeItem(tree, 5, { id: 6, pid: 5, value: "Item 5-1" }))
);
console.log(
  JSON.stringify(addTreeItem(tree, 6, { id: 7, pid: 6, value: "Item 6-1" }))
);
console.log(
  JSON.stringify(addTreeItem(tree, null, { id: 8, pid: null, value: "Item 3" }))
);

树:批量修改

/**
 * 树:批量修改, (节点映射新字段)
 * @param  treeData 数据源
 * @param  mapRule  自定义要映射的规则
 */

const mapTree = <T>(
  treeData: ITree[],
  mapRule: (item: ITree) => T
): (T & ITree)[] => {
  return treeData.map((item) => ({
    ...item,
    ...mapRule(item),
    children:
      item.children && item.children.length
        ? mapTree(item.children, mapRule)
        : null,
  }));
};

type INewField = {
  newVal: string;
  newField: string;
};
console.log(
  JSON.stringify(
    mapTree<INewField>(tree, (item) => ({
      newVal: "追加字段",
      newField: item.value.indexOf("1") > -1 ? "特殊标识" : "普通标识",
    }))
  )
);

树:查找树的最大层级

/**
 * 查找树的最大层级
 * @param  treeData 数据源
 * @returns {number}
 */
const findTreeDepth = (treeData: ITree[] | undefined): number => {
  if (!treeData || treeData.length === 0) {
    return 0;
  }
  let maxDepth = 0;
  for (let node of treeData) {
    const depth = findTreeDepth(node.children);
    maxDepth = Math.max(maxDepth, depth);
  }
  // 返回子节点中最大深度加上当前节点的深度
  return maxDepth + 1;
};
console.log(JSON.stringify(findTreeDepth(tree)));

总结:上述代码中

  • 方便同学理解,所有的方法都是围绕上面定义的【数据示例】进行代码编写。
  • 关于js 树的操作,写的都是最简单易懂的基础代码,特殊场景或特殊字段,需要自己在进行特殊改造。

有疑问的同学可以私信我、对帮助到同学欢迎大家收藏评论。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值