countDescendants
统计树形结构实体的后代节点数量。
方法签名
countDescendants(options: FindTreeOptions): Promise<number>
参数说明
FindTreeOptions
interface FindTreeOptions<T extends EntityType = any> {
/**
* 查询的根实体 ID
*/
entityId?: string;
/**
* 查询条件
*/
where?: RuleGroup<InstanceType<T>>;
/**
* 查询级别(深度)
* @default 0 表示查询所有层级
*/
level?: number;
}
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| entityId | string | 否 | - | 根节点的 ID,如果不指定则统计所有根节点的后代 |
| where | RuleGroup | 否 | - | 额外的过滤条件 |
| level | number | 否 | 0 | 查询深度,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);
注意事项
- 性能优势:
countDescendants只统计数量,性能优于findDescendants - 实时更新:统计结果会随着树结构的变化自动更新
- level 参数:
level: 0统计所有层级,level: 1只统计直接子节点 - 过滤条件:使用
where参数可以只统计符合条件的节点 - 实体定义:只能在使用
@TreeEntity装饰器定义的实体上使用此方法
相关方法
- findDescendants - 查询后代节点
- findAncestors - 查询祖先节点
- countAncestors - 统计祖先节点数量