树结构 (Tree)
当前核心包里的树结构基于邻接表模型:每个节点只有一个可空 parentId,父子关系靠自引用维护。
区分两个角色
@TreeEntity():仅负责将仓库类型默认设为TreeRepository,并补充树特性元数据TreeAdjacencyListEntityBase:真正提供parentId、parent$、children$、hasChildren和树查询静态方法
最常见且最稳妥的写法如下:
import { PropertyType, TreeAdjacencyListEntityBase, TreeEntity } from '@aiao/rxdb';
@TreeEntity({
name: 'Menu',
tableName: 'menu',
properties: [{ name: 'title', type: PropertyType.string }]
})
export class Menu extends TreeAdjacencyListEntityBase {}
基类自动带来的内容
TreeAdjacencyListEntityBase 已经内置:
- 计算属性
hasChildren children:ONE_TO_MANYparent:MANY_TO_ONEparentId- 树查询静态方法
其中 parent 关系是可空的,并且显式设置了:
onDelete: OnDeleteAction.CASCADE;
这意味着树结构默认就是“删父带子”。
关系访问
import { firstValueFrom } from 'rxjs';
const root = new Menu({ title: '根节点' });
const child = new Menu({ title: '子节点' });
child.parent$.set(root);
await child.save();
const parent = await firstValueFrom(child.parent$);
const children = await firstValueFrom(root.children$);
const count = await firstValueFrom(root.children$.count$);
你也可以直接改外键:
child.parentId = root.id;
await child.save();
树查询 API
树实体的静态方法返回 Observable:
import { firstValueFrom } from 'rxjs';
const descendants = await firstValueFrom(
Menu.findDescendants({
entityId: root.id,
level: 2
})
);
const ancestors = await firstValueFrom(
Menu.findAncestors({
entityId: child.id,
level: 3
})
);
const descendantCount = await firstValueFrom(
Menu.countDescendants({
entityId: root.id,
level: 2
})
);
FindTreeOptions 的真实含义
entityId?: string | nullwhere?: 普通查询条件level?: number
当前实现的 level 规则:
- 默认
0 - 小于
0会被归一化为0 - 大于
100会被限制为100
返回语义
findDescendants:指定entityId时,返回“当前节点 + 后代节点”countDescendants:指定entityId时,不包含当前节点,只统计后代数量findAncestors:指定entityId时,返回“当前节点 + 祖先节点”countAncestors:指定entityId时,不包含当前节点,只统计祖先数量
根节点查询
entityId 不传时,树查询以根节点集合为起点;框架内部会把 entityId 归一化为 null。
构建一棵简单树
const root = new Menu({ title: '根' });
await root.save();
const a = new Menu({ title: 'A' });
a.parent$.set(root);
await a.save();
const b = new Menu({ title: 'B' });
b.parentId = root.id;
await b.save();
建议
- 想要完整树能力,优先继承
TreeAdjacencyListEntityBase @TreeEntity()本身不生成父子关系,不要误认为单独使用它就会自动出现parentId- 祖先/后代查询用静态方法,普通列表过滤仍然走常规
find