跳到主要内容

countDescendants

统计树形结构实体的后代节点数量。

方法签名

countDescendants(options: FindTreeOptions): Promise<number>

参数说明

FindTreeOptions

interface FindTreeOptions<T extends EntityType = any> {
/**
* 查询的根实体 ID
*/
entityId?: string;

/**
* 查询条件
*/
where?: RuleGroup<InstanceType<T>>;

/**
* 查询级别(深度)
* @default 0 表示查询所有层级
*/
level?: number;
}
参数类型必填默认值说明
entityIdstring-根节点的 ID,如果不指定则统计所有根节点的后代
whereRuleGroup-额外的过滤条件
levelnumber0查询深度,0 表示统计所有层级,1 表示只统计直接子节点

基础用法

统计所有后代节点

import { Menu } from './entities/Menu';

// 统计指定菜单项的所有后代节点数量
const count = await Menu.countDescendants({
entityId: 'menu-root-id'
});

console.log('后代节点总数:', count);

限制统计深度

// 只统计直接子节点数量
const directChildCount = await Menu.countDescendants({
entityId: 'menu-root-id',
level: 1
});

// 统计两层深度的后代节点
const twoLevelsCount = await Menu.countDescendants({
entityId: 'menu-root-id',
level: 2
});

使用过滤条件

// 统计所有启用状态的后代节点
const activeCount = await Menu.countDescendants({
entityId: 'menu-root-id',
where: {
combinator: 'and',
rules: [
{ field: 'enabled', operator: '=', value: true }
]
}
});

// 统计特定类型的后代节点
const folderCount = await Menu.countDescendants({
entityId: 'menu-root-id',
where: {
combinator: 'and',
rules: [
{ field: 'type', operator: '=', value: 'folder' }
]
},
level: 3
});

响应式统计

使用 RxJS 订阅后代节点数量的实时变化:

import { Subscription } from 'rxjs';

// 订阅后代节点数量变化
const subscription: Subscription = Menu.countDescendants({
entityId: 'menu-root-id',
level: 2
}).subscribe({
next: (count) => {
console.log('后代节点数量:', count);
},
error: (err) => {
console.error('统计错误:', err);
}
});

// 取消订阅
subscription.unsubscribe();

实际应用场景

1. 显示子项数量徽章

```typescript
@Entity({
type: 'tree',
name: 'Menu',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'enabled', type: PropertyType.boolean, default: true }
]
})
export class Menu extends TreeEntityBase {}

// 为菜单项显示子项数量
async function getMenuWithCount(menuId: string) {
const menu = await Menu.get(menuId);
const childCount = await Menu.countDescendants({
entityId: menuId,
level: 1 // 只统计直接子项
});

return {
...menu,
childCount
};
}

2. 文件夹大小统计

@Entity({
type: 'tree',
name: 'Folder',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'type', type: PropertyType.string }
]
})
export class Folder extends TreeEntityBase {}

// 统计所有子文件夹数量
async function countSubFolders(folderId: string) {
const folderCount = await Folder.countDescendants({
entityId: folderId,
where: {
combinator: 'and',
rules: [
{ field: 'type', operator: '=', value: 'folder' }
]
}
});

return folderCount;
}

3. 组织架构统计

```typescript
@Entity({
type: 'tree',
name: 'Department',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'employeeCount', type: PropertyType.number }
]
})
export class Department extends TreeEntityBase {}

// 统计部门层级深度
async function getDepartmentDepth(rootDeptId: string) {
let depth = 0;
let hasChildren = true;

while (hasChildren) {
depth++;
const count = await Department.countDescendants({
entityId: rootDeptId,
level: depth
});

if (count === 0) {
hasChildren = false;
depth--;
}
}

return depth;
}

// 统计某个部门的所有下属部门数量
async function countSubDepartments(deptId: string) {
const count = await Department.countDescendants({
entityId: deptId
});

return count;
}

4. 树形列表懒加载提示

// 判断节点是否有子节点(用于显示展开图标)
async function hasChildren(menuId: string): Promise<boolean> {
const count = await Menu.countDescendants({
entityId: menuId,
level: 1
});

return count > 0;
}

// 获取带有子节点信息的列表
async function getMenuListWithChildInfo(parentId: string) {
const menus = await Menu.findDescendants({
entityId: parentId,
level: 1
});

const menusWithInfo = await Promise.all(
menus.map(async (menu) => ({
...menu,
hasChildren: await hasChildren(menu.id),
childCount: await Menu.countDescendants({
entityId: menu.id,
level: 1
})
}))
);

return menusWithInfo;
}

性能优化

1. 批量统计

对于需要统计多个节点的场景,考虑批量查询:

// ❌ 不推荐:逐个统计
const counts = [];
for (const menuId of menuIds) {
const count = await Menu.countDescendants({ entityId: menuId });
counts.push(count);
}

// ✅ 推荐:并行统计
const counts = await Promise.all(
menuIds.map(menuId =>
Menu.countDescendants({ entityId: menuId })
)
);

2. 缓存统计结果

const countCache = new Map<string, number>();

async function getCachedDescendantCount(menuId: string) {
if (countCache.has(menuId)) {
return countCache.get(menuId)!;
}

const count = await Menu.countDescendants({
entityId: menuId,
level: 1
});

countCache.set(menuId, count);
return count;
}

// 数据更新时清除缓存
function clearCountCache() {
countCache.clear();
}

3. 分层统计

对于深层树形结构,分层统计可以提高性能:

// 先统计直接子节点
const level1Count = await Menu.countDescendants({
entityId: rootId,
level: 1
});

// 如果需要更深层的统计,再按需查询
if (needDeepCount) {
const totalCount = await Menu.countDescendants({
entityId: rootId
});
}

与 findDescendants 的比较

方法用途返回值性能适用场景
countDescendants统计数量number更快只需要知道数量时
findDescendants查询详情Entity[]较慢需要具体数据时
// 只需要数量时使用 countDescendants
const count = await Menu.countDescendants({ entityId: menuId });
if (count > 0) {
console.log(`${count} 个子项`);
}

// 需要具体数据时使用 findDescendants
const children = await Menu.findDescendants({
entityId: menuId,
level: 1
});
console.log('子项列表:', children);

注意事项

  1. 性能优势countDescendants 只统计数量,性能优于 findDescendants
  2. 实时更新:统计结果会随着树结构的变化自动更新
  3. level 参数level: 0 统计所有层级,level: 1 只统计直接子节点
  4. 过滤条件:使用 where 参数可以只统计符合条件的节点
  5. 实体定义:只能在使用 @TreeEntity 装饰器定义的实体上使用此方法

相关方法

参考