npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tni/tree

v2.0.2

Published

通用树结构工具集,支持数组转树、遍历、查找、过滤、移动与层级修复。

Readme

@tni/tree

通用树结构工具集,面向“普通对象 + 可配置字段名”的数据模型,提供数组转树、树转数组、遍历、查询、过滤、移动、层级修复与 helper 预绑定能力。

设计目标

  • 结构可适配:通过 TreeConfig 自定义 idKeyparentIdKeychildrenKeylevelKeyisLeafKey,不强绑业务字段名。
  • API 语义稳定:对外保持单一入口,内部按基础设施、查询、变更、转换拆分,便于维护和扩展。
  • 尽量纯函数:大多数 API 返回新的树或数组副本,避免对传入数据产生隐式修改。
  • 查询与变更分层:查找、遍历、兄弟节点、祖先节点等只读能力与插入、删除、移动、更新等写操作分开维护。
  • 兼容平铺层级数据:提供 repairJSTreesupplementJSTreeLevel 处理层级不稳定或来自 jsTree 风格的数据。

目录结构

src/
  index.ts
  types.ts
  tree.ts
  core/
    config.ts
    node.ts
    match.ts
    visit.ts
  operations/
    transform.ts
    query.ts
    mutation.ts
    array.ts
tests/
  transform.test.ts
  query.test.ts
  mutation.test.ts
  helper.test.ts

快速开始

import {
  arrayToTree,
  createTreeHelper,
  filterTree,
  findTreePath,
  moveTreeNode,
  setTreeLevelAndLeaf,
  treeToArray,
} from "@tni/tree";

const rows = [
  { id: 1, parentId: null, name: "Root" },
  { id: 2, parentId: 1, name: "Design" },
  { id: 3, parentId: 1, name: "Engineering" },
  { id: 4, parentId: 2, name: "UI" },
];

const tree = arrayToTree(rows);
const path = findTreePath(tree, 4);
const filtered = filterTree(tree, (node) => node.name.includes("Design"));
const annotated = setTreeLevelAndLeaf(tree);
const moved = moveTreeNode(tree, 4, 3, { position: "inside" });
const flat = treeToArray(tree);

核心 API

| 方法名 | 语义 | 入参 | 出参 | | ----------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------- | | arrayToTree | 将平铺数组按 id / parentId 关系组装成树结构 | items: readonly T[], options?: ArrayToTreeOptions<T> | T[] | | treeToArray | 将树结构以前序遍历方式拍平成数组,并回填 parentId | tree: readonly T[], options?: TreeToArrayOptions<T> | T[] | | filterTree | 按谓词过滤树,保留命中节点及其必要祖先链 | tree: readonly T[], predicate: TreePredicate<T>, options?: FilterTreeOptions<T> | T[] | | findTreeNode | 查找首个命中的树节点 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T \| undefined | | findTreeNodes | 查找全部命中的树节点 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[] | | findTreePath | 查找首个命中节点从根到当前节点的路径 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[] | | findTreePaths | 查找全部命中节点各自的路径 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[][] | | traverseTree | 深度优先遍历整棵树,可配合 SKIP_CHILDREN / STOP_TRAVERSAL 控制流程 | tree: readonly T[], visitor: TreeVisitor<T>, config?: TreeConfig<T> | void | | traverseLeafNodes | 只遍历叶子节点 | tree: readonly T[], visitor: TreeVisitor<T>, config?: TreeConfig<T> | void | | getSiblingNodes | 获取目标节点的同级节点 | tree: readonly T[], matcher: TreeMatch<T>, options?: GetSiblingNodesOptions<T> | T[] | | getChildNodes | 获取目标节点的直接子节点或全部后代节点 | tree: readonly T[], matcher: TreeMatch<T>, options?: GetChildNodesOptions<T> | T[] | | getAncestorNode | 获取目标节点指定距离的祖先节点 | tree: readonly T[], matcher: TreeMatch<T>, options?: GetAncestorNodeOptions<T> | T \| undefined | | getLeafNodes | 获取整棵树中的全部叶子节点 | tree: readonly T[], config?: TreeConfig<T> | T[] | | getNodeChain | 获取目标节点的节点链,本质上等同于路径查询 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[] | | getTreeMaxDepth | 获取树的最大深度 | tree: readonly T[], config?: TreeConfig<T> | number | | countLeafNodes | 统计叶子节点数量 | tree: readonly T[], config?: TreeConfig<T> | number | | findNodeById | 按节点 id 快速查找节点 | tree: readonly T[], id: string \| number \| symbol, config?: TreeConfig<T> | T \| undefined | | insertNodeBefore | 在目标节点前插入新节点 | tree: readonly T[], oldNode: TreeMatch<T>, newNode: T, config?: TreeConfig<T> | T[] | | insertNodeAfter | 在目标节点后插入新节点 | tree: readonly T[], oldNode: TreeMatch<T>, newNode: T, config?: TreeConfig<T> | T[] | | removeTreeNode | 删除首个命中的节点 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[] | | removeTreeNodes | 删除全部命中的节点 | tree: readonly T[], matcher: TreeMatch<T>, config?: TreeConfig<T> | T[] | | updateTreeNode | 更新首个命中的节点 | tree: readonly T[], matcher: TreeMatch<T>, updater: TreeUpdater<T>, config?: TreeConfig<T> | T[] | | moveTreeNode | 将源节点移动到目标节点前、后或内部 | tree: readonly T[], source: TreeMatch<T>, target: TreeMatch<T>, options?: MoveTreeNodeOptions<T> | T[] | | getArrayChildren | 在平铺数组中查找某个父节点的直接子节点 | items: readonly T[], parent: TreeMatch<T>, config?: TreeConfig<T> | T[] | | getArrayParent | 在平铺数组中查找某个节点的父节点 | items: readonly T[], node: TreeMatch<T>, config?: TreeConfig<T> | T \| undefined | | setTreeLevelAndLeaf | 为树补齐层级字段和叶子节点标记 | tree: readonly T[], options?: SetTreeLevelOptions<T> | T[] | | repairJSTree | 修复 jsTree 风格平铺数据中的层级、父子关系和叶子标记 | items: readonly T[], options?: RepairJSTreeOptions<T> | T[] | | supplementJSTreeLevel | 将 jsTree 风格平铺数据的根层级整体平移到目标层级 | items: readonly T[], targetLevel: number, options?: SupplementJSTreeLevelOptions<T> | T[] | | createTreeHelper | 预绑定一组 TreeConfig,返回同构的 helper 方法集合 | config?: TreeConfig<T> | TreeHelpers<T> |

简单用法与输出

以下示例共用同一份数据,输出做了适度简化,只保留关键字段。

type Node = {
  id: number;
  parentId: number | null;
  name: string;
  children?: Node[];
  level?: number;
  isLeaf?: boolean;
};

const rows: Node[] = [
  { id: 1, parentId: null, name: "Root" },
  { id: 2, parentId: 1, name: "Design" },
  { id: 3, parentId: 1, name: "Engineering" },
  { id: 4, parentId: 2, name: "UI" },
  { id: 5, parentId: 3, name: "API" },
];

const tree = arrayToTree(rows);

转换类

const tree = arrayToTree(rows);
// => [
//      {
//        id: 1,
//        name: "Root",
//        children: [
//          { id: 2, name: "Design", children: [{ id: 4, name: "UI", children: [] }] },
//          { id: 3, name: "Engineering", children: [{ id: 5, name: "API", children: [] }] },
//        ],
//      },
//    ]

const flat = treeToArray(tree);
// => [
//      { id: 1, parentId: null, name: "Root" },
//      { id: 2, parentId: 1, name: "Design" },
//      { id: 4, parentId: 2, name: "UI" },
//      { id: 3, parentId: 1, name: "Engineering" },
//      { id: 5, parentId: 3, name: "API" },
//    ]

const filtered = filterTree(tree, (node) => node.name.includes("Design"));
// => [
//      {
//        id: 1,
//        children: [{ id: 2, name: "Design", children: [] }],
//      },
//    ]

const leveled = setTreeLevelAndLeaf(tree);
// => 根节点 { id: 1, level: 1, isLeaf: false }
// => UI 节点 { id: 4, level: 3, isLeaf: true }

const repaired = repairJSTree([
  { id: 10, parentId: null, name: "A", level: 1 },
  { id: 11, parentId: null, name: "B", level: 4 },
  { id: 12, parentId: null, name: "C", level: 2 },
]);
// => [
//      { id: 10, level: 1, parentId: null, isLeaf: false },
//      { id: 11, level: 2, parentId: 10, isLeaf: true },
//      { id: 12, level: 2, parentId: 10, isLeaf: true },
//    ]

const supplemented = supplementJSTreeLevel(
  [
    { id: 10, parentId: null, name: "A", level: 1 },
    { id: 11, parentId: 10, name: "B", level: 2 },
  ],
  3,
);
// => [
//      { id: 10, level: 3, parentId: null },
//      { id: 11, level: 4, parentId: 10 },
//    ]

查询类

const one = findTreeNode(tree, 4);
// => { id: 4, parentId: 2, name: "UI", children: [] }

const many = findTreeNodes(tree, (node) => node.name.includes("i"));
// => [{ id: 2, name: "Design" }, { id: 3, name: "Engineering" }, { id: 4, name: "UI" }]

const path = findTreePath(tree, 4);
// => [{ id: 1, name: "Root" }, { id: 2, name: "Design" }, { id: 4, name: "UI" }]

const paths = findTreePaths(tree, (node) => node.parentId === 1);
// => [
//      [{ id: 1, name: "Root" }, { id: 2, name: "Design" }],
//      [{ id: 1, name: "Root" }, { id: 3, name: "Engineering" }],
//    ]

const chain = getNodeChain(tree, 5);
// => [{ id: 1, name: "Root" }, { id: 3, name: "Engineering" }, { id: 5, name: "API" }]

const siblings = getSiblingNodes(tree, 2);
// => [{ id: 3, name: "Engineering" }]

const children = getChildNodes(tree, 1);
// => [{ id: 2, name: "Design" }, { id: 3, name: "Engineering" }]

const descendants = getChildNodes(tree, 1, { deep: true });
// => [
//      { id: 2, name: "Design" },
//      { id: 4, name: "UI" },
//      { id: 3, name: "Engineering" },
//      { id: 5, name: "API" },
//    ]

const ancestor = getAncestorNode(tree, 4);
// => { id: 2, name: "Design" }

const rootAncestor = getAncestorNode(tree, 4, { distance: 2 });
// => { id: 1, name: "Root" }

const leaves = getLeafNodes(tree);
// => [{ id: 4, name: "UI" }, { id: 5, name: "API" }]

const maxDepth = getTreeMaxDepth(tree);
// => 3

const leafCount = countLeafNodes(tree);
// => 2

const byId = findNodeById(tree, 3);
// => { id: 3, parentId: 1, name: "Engineering", children: [{ id: 5, name: "API" }] }
const visited: string[] = [];

traverseTree(tree, (node) => {
  visited.push(node.name);
});

// => ["Root", "Design", "UI", "Engineering", "API"]

const leafVisited: string[] = [];

traverseLeafNodes(tree, (node) => {
  leafVisited.push(node.name);
});

// => ["UI", "API"]

变更类

const beforeInserted = insertNodeBefore(tree, 3, { id: 6, parentId: 1, name: "QA" });
// => Root.children 顺序变为 ["Design", "QA", "Engineering"]

const afterInserted = insertNodeAfter(tree, 2, { id: 6, parentId: 1, name: "QA" });
// => Root.children 顺序变为 ["Design", "QA", "Engineering"]

const removedOne = removeTreeNode(tree, 4);
// => Design.children 变为 []

const removedMany = removeTreeNodes(tree, (node) => node.parentId === 1);
// => [{ id: 1, name: "Root", children: [] }]

const updated = updateTreeNode(tree, 5, { name: "Backend API" });
// => id=5 的节点变为 { id: 5, name: "Backend API" }

const movedInside = moveTreeNode(tree, 4, 3, { position: "inside" });
// => UI 从 Design.children 移到 Engineering.children

const movedBefore = moveTreeNode(tree, 3, 2, { position: "before" });
// => Root.children 顺序变为 ["Engineering", "Design"]

平铺数组关系查询

const arrayChildren = getArrayChildren(rows, 1);
// => [{ id: 2, name: "Design" }, { id: 3, name: "Engineering" }]

const arrayParent = getArrayParent(rows, 4);
// => { id: 2, parentId: 1, name: "Design" }

Helper

const helper = createTreeHelper<Node>({
  idKey: "id",
  parentIdKey: "parentId",
  childrenKey: "children",
});

const helperTree = helper.arrayToTree(rows);
// => 与 arrayToTree(rows) 输出一致

const helperNode = helper.findTreeNode(helperTree, 5);
// => { id: 5, parentId: 3, name: "API", children: [] }

const helperMoved = helper.moveTreeNode(helperTree, 4, 3, { position: "inside" });
// => 与 moveTreeNode(tree, 4, 3, { position: "inside" }) 输出一致

遍历控制

import { SKIP_CHILDREN, STOP_TRAVERSAL, traverseTree } from "@tni/tree";

traverseTree(tree, (node) => {
  if (node.type === "lazy") {
    return SKIP_CHILDREN;
  }
  if (node.type === "stop") {
    return STOP_TRAVERSAL;
  }
});

验证

vp run tree#typecheck
vp test packages/tree
vp check

许可证

MIT