1.0.3 • Published 5 months ago

js-tree-toolkit v1.0.3

Weekly downloads
-
License
ISC
Repository
-
Last release
5 months ago

数组转树

function arrayToTree(array, parentIdKey = 'parentId', childKey = 'children') {
  const tree = [];
  const map = {};
  array.forEach(item => map[item.id] = item);
  array.forEach(item => {
    const parent = map[item[parentIdKey]];
    if (parent) {
      (parent[childKey] || (parent[childKey] = [])).push(item);
    } else {
      tree.push(item);
    }
  });
  return tree;
}

测试代码

const data = [
  { id: 1, pId: null, name: 'A' },
  { id: 2, pId: 1, name: 'B' },
  { id: 3, pId: 1, name: 'C' },
  { id: 4, pId: 2, name: 'D' },
  { id: 5, pId: null, name: 'E' }
];

const tree = arrayToTree(data, 'pId', 'subItems');

树转数组

function treeToArray(tree, parentIdKey = 'parentId', childKey = 'children') {
  const array = [];
  const stack = [...tree];
  while (stack.length > 0) {
    const node = stack.pop();
    const item = { ...node };
    delete item[childKey];
    array.push(item);
    if (node[childKey]) {
      node[childKey].forEach(child => {
        child[parentIdKey] = node.id;
        stack.push(child);
      });
    }
  }
  return array;
}

测试代码

const tree = [
  {
    id: 1,
    name: 'A',
    children: [
      {
        id: 2,
        name: 'B',
        children: [
          {
            id: 4,
            name: 'D'
          }
        ]
      },
      {
        id: 3,
        name: 'C'
      }
    ]
  },
  {
    id: 5,
    name: 'E'
  }
];

const array = treeToArray(tree);

console.log(array);

树遍历的方法

  function traverseTree(tree, callback, childKey = 'children') {
      if (!Array.isArray(tree)) {
          return 'Invalid tree data: tree must be an array';
      }
      if (typeof callback !== 'function') {
          return 'Invalid callback: callback must be a function';
      }
      for (const node of tree) {
          callback(node);
          if (node[childKey]) {
              if (!Array.isArray(node[childKey])) {
                  return `Invalid tree data: ${childKey} must be an array`;
              }
              const result = traverseTree(node[childKey], callback, childKey);
              if (result) {
                  return result;
              }
          }
      }
  }

测试代码

const tree = [
  {
    id: 1,
    name: 'A',
    children: [
      {
        id: 2,
        name: 'B',
        children: [
          {
            id: 4,
            name: 'D'
          }
        ]
      },
      {
        id: 3,
        name: 'C'
      }
    ]
  },
  {
    id: 5,
    name: 'E'
  }
];

const result = traverseTree(tree, node => console.log(node.name));
if (result) {
  console.error(result);
}

js tree修复方法

  function fixTree(tree, childKey = 'children') {
      if (!Array.isArray(tree)) {
          return;
      }
      tree.forEach(node => {
          if (!Array.isArray(node[childKey])) {
              node[childKey] = [];
          }
          fixTree(node[childKey], childKey);
      });
  }

测试代码

const tree = [
  {
    id: 1,
    name: 'A',
    children: [
      {
        id: 2,
        name: 'B',
        children: [
          {
            id: 4,
            name: 'D'
          }
        ]
      },
      {
        id: 3,
        name: 'C'
      }
    ]
  },
  {
    id: 5,
    name: 'E',
    children: 'invalid'
  }
];

fixTree(tree);

console.log(tree);

获取树最大深度方法

function getMaxDepth(tree, childKey = 'children') {
        if (!Array.isArray(tree) || tree.length === 0) {
            return 0;
        }
        let maxDepth = 0;
        tree.forEach(node => {
            maxDepth = Math.max(maxDepth, getMaxDepth(node[childKey], childKey));
        });
        return maxDepth + 1;
  }

测试代码

const tree = [
  {
    id: 1,
    name: 'A',
    children: [
      {
        id: 2,
        name: 'B',
        children: [
          {
            id: 4,
            name: 'D'
          }
        ]
      },
      {
        id: 3,
        name: 'C'
      }
    ]
  },
  {
    id: 5,
    name: 'E'
  }
];

const maxDepth = getMaxDepth(tree);

console.log(maxDepth);

获取根节点到当前节点数据的方法

function getSubTree(tree, findNodeFunc, childKey = 'children', includeCurrentNode = true) {
        let subTree = null;
        for (let i = 0; i < tree.length; i++) {
            if (findNodeFunc(tree[i])) {
                return includeCurrentNode ? tree[i] : null;
            }
            if (tree[i][childKey]) {
                subTree = getSubTree(tree[i][childKey], findNodeFunc, childKey, includeCurrentNode);
                if (subTree) {
                    return { ...tree[i], [childKey]:[subTree] };
                }
            }
        }
        return subTree;
    }

测试数据

const tree = [
    {
        id: 1,
        children: [
            {
                id: 2,
                children: [
                    {
                        id: 4
                    },
                    {
                        id: 5
                    }
                ]
            },
            {
                id: 3
            }
        ]
    }
];

const subTree = getSubTree(tree, node => node.id === 5);
console.log(subTree);
/*
{
    id: 1,
    children: [
        {
            id: 2,
            children: [
                {
                    id: 5
                }
            ]
        }
    ]
}
*/

获取兄弟节点方法

    function getSiblingNodes(tree, nodeFunc, childKey = 'children', includeCurrentNode = false) {
        let siblings = [];
        let parent = null;
        let currentNode = null;
        let findParentAndCurrentNode = (tree) => {
            if (Array.isArray(tree)) {
                for (let i = 0; i < tree.length; i++) {
                    if (nodeFunc(tree[i])) {
                        parent = tree;
                        currentNode = tree[i];
                        return;
                    } else {
                        findParentAndCurrentNode(tree[i]);
                    }
                }
            } else if (tree[childKey]) {
                for (let i = 0; i < tree[childKey].length; i++) {
                    if (nodeFunc(tree[childKey][i])) {
                        parent = tree;
                        currentNode = tree[childKey][i];
                        return;
                    } else {
                        findParentAndCurrentNode(tree[childKey][i]);
                    }
                }
            }
        }
        findParentAndCurrentNode(tree);
        if (parent) {
            siblings = parent[childKey].filter(n => includeCurrentNode || n !== currentNode);
        }
        return siblings;
    }

测试方法

let tree = {
    name: 'root',
    children: [
        {
            name: 'a',
            children: [
                { name: 'a1' },
                { name: 'a2' }
            ]
        },
        {
            name: 'b',
            children: [
                { name: 'b1' },
                { name: 'b2' }
            ]
        }
    ]
};

let nodeFunc = (node) => node.name === 'a1';
let siblings = getSiblingNodes(tree, nodeFunc);
console.log(siblings); // 输出:[ { name: 'a2' } ]

获取子节点方法

function getChildren(node, filterFn, childKey = 'children', includeCurrent = true) {
        let result = [];
        if (includeCurrent && filterFn(node)) {
            result.push(node);
        }
        if (node[childKey]) {
            node[childKey].forEach(child => {
                result = result.concat(getChildren(child, filterFn, childKey, includeCurrent));
            });
        }
        return result;
    }

测试代码

const tree = {
  id: 1,
  children: [
    {
      id: 2,
      children: [
        {
          id: 3
        },
        {
          id: 4
        }
      ]
    },
    {
      id: 5,
      children: [
        {
          id: 6
        }
      ]
    }
  ]
};

console.log(getChildren(tree, node => node.id == 2));

获取叶子节点方法

function getLeafNodes(node, childKey = 'children') {
    let result = [];
    if (!node[childKey] || node[childKey].length === 0) {
        result.push(node);
    } else {
        node[childKey].forEach(child => {
            result = result.concat(getLeafNodes(child, childKey));
        });
    }
    return result;
}

测试代码

const tree = {
  id: 1,
  subNodes: [
    {
      id: 2,
      subNodes: [
        {
          id: 3
        },
        {
          id: 4
        }
      ]
    },
    {
      id: 5,
      subNodes: [
        {
          id: 6
        }
      ]
    }
  ]
};

console.log(getLeafNodes(tree, 'subNodes'));

遍历树叶子节点

  function traverseLeafNodes(node, callback, childKey = 'children') {
      if (!node[childKey] || node[childKey].length === 0) {
          callback(node);
      } else {
          node[childKey].forEach(child => {
              traverseLeafNodes(child, callback, childKey);
          });
      }
  }

测试代码

const tree = {
  id: 1,
  subNodes: [
    {
      id: 2,
      subNodes: [
        {
          id: 3
        },
        {
          id: 4
        }
      ]
    },
    {
      id: 5,
      subNodes: [
        {
          id: 6
        }
      ]
    }
  ]
};

traverseLeafNodes(tree, node => console.log(node.id), 'subNodes');

设置树的层级和是否叶子节点

   function setLeafNodesAndLevel(nodeOrNodes, level = 1, childKey = 'children') {
        if (Array.isArray(nodeOrNodes)) {
            // 如果参数是数组,则处理每个节点
            nodeOrNodes.forEach(node => {
                node.level = level;
                if (!node[childKey] || node[childKey].length === 0) {
                    node.isLeaf = true;
                } else {
                    node.isLeaf = false;
                    setLeafNodesAndLevel(node[childKey], level + 1, childKey);
                }
            });
        } else if (typeof nodeOrNodes === 'object') {
            // 如果参数是单个对象,则处理该对象
            nodeOrNodes.level = level;
            if (!nodeOrNodes[childKey] || nodeOrNodes[childKey].length === 0) {
                nodeOrNodes.isLeaf = true;
            } else {
                nodeOrNodes.isLeaf = false;
                setLeafNodesAndLevel(nodeOrNodes[childKey], level + 1, childKey);
            }
        } else {
            throw new Error('Invalid input. Expected an object or an array.');
        }
    }

测试代码

//入参为对象
const tree = {
  id: 1,
  subNodes: [
    {
      id: 2,
      subNodes: [
        {
          id: 3
        },
        {
          id: 4
        }
      ]
    },
    {
      id: 5,
      subNodes: [
        {
          id: 6
        }
      ]
    }
  ]
};

setLeafNodesAndLevel(tree, 0, 'subNodes');
console.log(tree);

//入参是数组
const inputArray = [
    {
        name: 'Node 1',
        children: [
            {
                name: 'Node 1.1',
                children: [
                    {
                        name: 'Node 1.1.1',
                        children: []
                    },
                    {
                        name: 'Node 1.1.2',
                        children: []
                    }
                ]
            },
            {
                name: 'Node 1.2',
                children: []
            }
        ]
    },
    {
        name: 'Node 2',
        children: [
            {
                name: 'Node 2.1',
                children: []
            },
            {
                name: 'Node 2.2',
                children: []
            }
        ]
    }
];

    // 执行函数
setLeafNodesAndLevel(inputArray);

获取指定层级父节点的方法 默认获取父亲

 function getParentNode(tree, filterFn, childKey = 'children') {
        if (!tree || typeof tree !== 'object') {
            return null;
        }

        function searchParentNode(node, parent) {
            if (filterFn(node)) {
                return parent;
            }

            if (Array.isArray(node[childKey])) {
                for (const child of node[childKey]) {
                    const result = searchParentNode(child, node);
                    if (result) {
                        return result;
                    }
                }
            }

            return null;
        }

        return searchParentNode(tree, null);
    }

测试代码

// 示例用法:
const tree = {
  name: 'A',
  children: [
    {
      name: 'B',
      children: [
        {
          name: 'C',
          children: [
            {
              name: 'D',
              children: [
                {
                  name: 'E',
                },
              ],
            },
          ],
        },
      ],
    },
  ],
};

const filterFn = (node) => node.name === 'C';
const parentNode = getParentNode(tree, filterFn);
console.log(parentNode); // 输出: { name: 'B', children: [...] }

计算叶子节点的个数

  function countLeafNodes(node, childKey = 'children') {
      if (!node[childKey] || node[childKey].length === 0) {
          return 1;
      } else {
          let count = 0;
          node[childKey].forEach(child => {
              count += countLeafNodes(child, childKey);
          });
          return count;
      }
  }

测试代码

const tree = {
  id: 1,
  subNodes: [
    {
      id: 2,
      subNodes: [
        {
          id: 3
        },
        {
          id: 4
        }
      ]
    },
    {
      id: 5,
      subNodes: [
        {
          id: 6
        }
      ]
    }
  ]
};

console.log(countLeafNodes(tree, 'subNodes'));
// 输出: 3

把js tree的层级补充到指定层级

function fillTree(tree, depth, fillFunction, childKey = 'children') {
    if (Array.isArray(tree)) {
        tree.forEach(item => fillTree(item, depth, fillFunction, childKey));
    } else {
        if (depth < 1) return;
        if (!tree[childKey]) tree[childKey] = [];
        if (tree[childKey].length === 0) tree[childKey].push(fillFunction());
        tree[childKey].forEach(child => fillTree(child, depth - 1, fillFunction, childKey));
    }
}

测试代码

let tree = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        {
          value: 3
        }
      ]
    },
    {
      value: 4
    }
  ]
};

fillTree(tree, 3, () => ({value: 0}));
console.log(JSON.stringify(tree, null, 2));

获取数组中对应的子节点

function getArrayChildren(data, filterFn = () => true, includeCurrent = true, pIdKey = 'pId', idKey = 'id') {
    let result = [];
    let current = data.find(filterFn);
    if (current && includeCurrent) result.push(current);

    const getChildrenRecursively = (parentId) => {
        data.forEach(item => {
            if (item[pIdKey] === parentId && filterFn(item)) {
                result.push(item);
                getChildrenRecursively(item[idKey]);
            }
        });
    };

    if (current) getChildrenRecursively(current[idKey]);
    return result;
}

测试代码

const data = [
    { id: 1, pId: null, name: 'Node 1' },
    { id: 2, pId: 1, name: 'Node 1.1' },
    { id: 3, pId: 1, name: 'Node 1.2' },
    { id: 4, pId: 2, name: 'Node 1.1.1' },
    { id: 5, pId: 2, name: 'Node 1.1.2' },
    { id: 6, pId: null, name: 'Node 2' },
    { id: 7, pId: 6, name: 'Node 2.1' },
    { id: 8, pId: 6, name: 'Node 2.2' },
];

const result = getArrayChildren(data, item => item.name.includes('Node 1'), true, 'pId', 'id');
console.log(result);

获取数组中父节点的方法

  function getArrayParent(data, filterFn = () => true, includeCurrent = true, pIdKey = 'parentId', idKey = 'id') {
      let result = [];
      let current = data.find(filterFn);
      if (current && includeCurrent) result.push(current);

      while (current && current[pIdKey] !== null) {
          current = data.find(item => item[idKey] === current[pIdKey]);
          if (current) {
              result.push(current);
          }
      }

      return result;
  }

测试代码

const data = [
    { id: 1, parentId: null, name: 'Node 1' },
    { id: 2, parentId: 1, name: 'Node 1.1' },
    { id: 3, parentId: 1, name: 'Node 1.2' },
    { id: 4, parentId: 2, name: 'Node 1.1.1' },
    { id: 5, parentId: 2, name: 'Node 1.1.2' },
    { id: 6, parentId: null, name: 'Node 2' },
    { id: 7, parentId: 6, name: 'Node 2.1' },
    { id: 8, parentId: 6, name: 'Node 2.2' },
];

const result = getArrayParent(data, item => item.name.includes('Node 1'), true, 'parentId', 'id');
console.log(result);

树节点移动方法

function moveTreeNode(treeData, sourceNodeFilterFn, targetNodeFilterFn, childKey = 'children') {
    let sourceNode = null;
    let targetNode = null;
    let findNodesRecursively = (data) => {
        data.forEach(item => {
            if (sourceNodeFilterFn(item)) sourceNode = item;
            if (targetNodeFilterFn(item)) targetNode = item;
            if (item[childKey]) findNodesRecursively(item[childKey]);
        });
    }
    findNodesRecursively(treeData);
    if (!sourceNode || !targetNode) return;
    let removeSourceNodeRecursively = (data) => {
        data.forEach((item, index) => {
            if (item === sourceNode) data.splice(index, 1);
            else if (item[childKey]) removeSourceNodeRecursively(item[childKey]);
        });
    }
    removeSourceNodeRecursively(treeData);
    if (!targetNode[childKey]) targetNode[childKey] = [];
    targetNode[childKey].push(sourceNode);
}

测试数据

let treeData = [
    {
        id: 1,
        name: 'A',
        children: [
            {id: 2, name: 'B'},
            {id: 3, name: 'C'}
        ]
    },
    {
        id: 4,
        name: 'D',
        children: [
            {id: 5, name: 'E'},
            {id: 6, name: 'F'}
        ]
    }
];
moveTreeNode(treeData, item => item.name === 'B', item => item.name === 'F');
console.log(JSON.stringify(treeData));

查找指定节点方法

    function findNode(tree, filterFn, childKey = 'children') {
        if (Array.isArray(tree)) {
            for (let node of tree) {
                let result = findNode(node, filterFn, childKey);
                if (result) {
                    return result;
                }
            }
        } else {
            if (filterFn(tree)) {
                return tree;
            }
            if (tree[childKey]) {
                for (let child of tree[childKey]) {
                    let result = findNode(child, filterFn, childKey);
                    if (result) {
                        return result;
                    }
                }
            }
        }
        return null;
    }

测试数据

// 定义树结构
const tree = {
    id: 1,
    children: [
        {
            id: 2,
            children: [
                {
                    id: 3,
                    name: 'Node 3'
                },
                {
                    id: 4,
                    name: 'Node 4'
                }
            ]
        },
        {
            id: 5,
            children: [
                {
                    id: 6,
                    name: 'Node 6'
                }
            ]
        }
    ]
};
  // 测试条件:查找 id 为 3 的节点
  const resultNode = findNode(tree, node => node.id === 3, 'children');
  
  if (resultNode) {
      console.log('找到节点:', resultNode);
  } else {
      console.log('未找到节点。');
  }

删除树指定节点

   function deleteNodes(tree, filterFn) {
        if (Array.isArray(tree)) {
            for (let i = tree.length - 1; i >= 0; i--) {
                if (filterFn(tree[i])) {
                    tree.splice(i, 1);
                } else if (typeof tree[i] === 'object') {
                    deleteNodes(tree[i], filterFn);
                }
            }
        } else if (typeof tree === 'object') {
            for (const key in tree) {
                if (tree.hasOwnProperty(key) && typeof tree[key] === 'object') {
                    deleteNodes(tree[key], filterFn);
                }
            }
        }
    }

测试数据

// 示例用法:
const tree = {
  name: 'A',
  children: [
    {
      name: 'B',
      children: [
        {
          name: 'C',
          children: [
            {
              name: 'D',
              children: [
                {
                  name: 'E',
                },
              ],
            },
          ],
        },
      ],
    },
  ],
};

const filterFn = (node) => node.name === 'E';
deleteNodes(tree, filterFn);

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

修改树节点信息

function modifyNodes(tree, filterFn, callback, childKey = 'children') {
  if (Array.isArray(tree)) {
    for (let i = 0; i < tree.length; i++) {
      if (filterFn(tree[i])) {
        tree[i] = callback(tree[i]);
      }

      if (tree[i][childKey] && Array.isArray(tree[i][childKey])) {
        modifyNodes(tree[i][childKey], filterFn, callback, childKey);
      }
    }
  } else if (typeof tree === 'object') {
    if (tree[childKey] && Array.isArray(tree[childKey])) {
      modifyNodes(tree[childKey], filterFn, callback, childKey);
    }
  }
}

测试代码

// 示例用法:
const tree = {
  name: 'A',
  children: [
    {
      name: 'B',
      children: [
        {
          name: 'C',
          children: [
            {
              name: 'D',
              children: [
                {
                  name: 'E',
                },
              ],
            },
          ],
        },
      ],
    },
  ],
};

const filterFn = (node) => node.name === 'C' || node.name === 'E';
const callback = (node) => {
  // 修改节点数据
  return {
    ...node,
    name: node.name + '_Modified',
  };
};

modifyNodes(tree, filterFn, callback);

console.log(JSON.stringify(tree, null, 2));
1.0.3

5 months ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago