递归调用 Firestore
Recursive calls to Firestore
我有一个 Firebase Firestore,其中 "Components" 作为根集合。集合中的每个文档("Component")可能有一个名为 "children" 的数组,数组中的每个项目都是另一个组件的 "id",本质上是一个 one-to-many 关系.由于每个 child 也是一个组件,它也可能有自己的 children 等等。
组件集合
Parent
1_Parent (document)
│ name: 'Parent'
│ id: '1_Parent'
└─children (array)
├─1.1_Child_one
└─1.2_Child_two
第一个Child
1.1_Child_one (document)
name: 'Child One'
id: '1.1_Child_one'
第二个Child
1.2_Child_two (document)
│ name: 'Child Two'
│ id: '1.2_Child_two'
└─children (array)
├─1.2.1_Grandchild_one
└─1.2.2_Grandchild_two
第一名child
1.2.1_Grandchild_one (document)
name: 'Grandchild One'
id: '1.2.1_Grandchild_one'
二等奖child
1.2.2_Grandchild_two (document)
name: 'Grandchild Two'
id: '1.2.2_Grandchild_two'
在我的代码中,我想为每个组件创建一个 object,如果它有一个 children 数组,那么数组中的每个 id 都被完全成熟的 object 从 Firestore 检索。
输出 object 树应该是这样的
1_Parent
│ name: 'Parent'
│ id: '1_Parent'
└─children
├─1.1_Child_one
│ name: 'Child One'
│ id: '1.1_Child_one'
└─1.2_Child_two
│ name: 'Child Two'
│ id: '1.2_Child_two'
└─children
├─1.2.1_grandhild_one
│ name: 'Grandchild One'
│ id: '1.2.1_grandhild_one'
└─1.2.2_grandhild_two
name: 'Grandchild Two'
id: '1.2.2_grandhild_two'
作为 JSON 的输出 object 应如下所示
{
"name": "Parent",
"id": "1_Parent",
"children": [
{
"name": "Child One",
"id": "1.1_Child_one"
},
{
"name": "Child Two",
"id": "1.2_Child_two",
"children": [
{
"name": "Grandchild One",
"id": "1.2.1_Grandchild_one"
},
{
"name": "Grandchild Two",
"id": "1.2.2_Grandchild_two"
}
]
}
]
}
很明显,我们在这里需要递归,但我完全不知道如何使用 RxJS 创建递归函数。如果允许这样做,我将不胜感激一些提示或示例代码。
请注意,我在 Angular 项目中使用它,并且我正在使用 AngularFire 访问 Firebase-Firestore。
RxJS 中的递归最好使用 expand
运算符来解决。您为它提供一个投影函数,该函数 returns 一个 Observable,在收到通知时,再次使用发出的值调用投影函数。只要您的内部 Observable 不发射 EMPTY
或 complete
,它就会这样做。
在这样做的同时,每个通知也会转发给 expand
的订阅者,这与传统的递归不同,传统的递归只会在最后得到结果。
来自 expand
上的官方文档:
Recursively projects each source value to an Observable which is merged in the output Observable.
让我们看看你的例子。如果没有 RxJS,如果我们有一个同步数据源给我们一个节点的每个子节点(我们称它为 getChildById
),该函数可能如下所示:
function createFamilyTree(node) {
node.children = node.childrenIds.map(childId =>
createFamilyTree(
getChildById(childId)
)
);
return node;
}
现在,我们将使用 expand
运算符将其转换为 RxJS:
parentDataSource$.pipe(
map(parent => ({node: parent})),
expand(({node}) => // expand is called for every node recursively
(i.e. the parent and each child, then their children and so on)
!node ? EMPTY : // if there is no node, end the recursion
from(node.childrenIds) // we'll convert the array of children
ids to an observable stream
.pipe(
mergeMap(childId => getChildById(childId) // and get the child for
the given id
.pipe(
map(child => ({node: child, parent: node}))) // map it, so we have
the reference to the
parent later on
)
)
),
// we'll get a bunch of notifications, but only want the "root",
that's why we use reduce:
reduce((acc, {node, parent}) =>
!parent ?
node : // if we have no parent, that's the root node and we return it
parent.children.push(node) && acc // otherwise, we'll add children
)
);
使用的运算符在RxJS官方文档页面有详细解释:from, expand, reduce
编辑:可以在此处找到上述代码的干净且经过测试的版本:https://stackblitz.com/edit/rxjs-vzxhqf?devtoolsheight=60
您可以按照这些思路寻找解决方案
// simulates an async data fetch from a remote db
function getComponent(id) {
return of(components.find(comp => comp.id === id)).pipe(delay(10));
}
function expandComp(component: Observable<any>) {
return component.pipe(
tap(d => console.log('s', d.name)),
mergeMap(component => {
if (component.childrenIds) {
return concat(
of(component).pipe(tap(comp => comp['children'] = [])),
from(component.childrenIds).pipe(
mergeMap(childId => expandComp(getComponent(childId)))
)
)
.pipe(
reduce((parent: any, child) => {
parent.children.push(child);
return parent;
})
)
}
else {
return of(component);
}
})
)
}
.subscribe(d => // do stuff, e.g. console.log(JSON.stringify(d, null, 3)))
我用下面的测试数据测试了上面的代码
const componentIds: Array<string> = [
'1',
'1.1.2'
]
const components: Array<any> = [
{id: '1', name: 'one', childrenIds: ['1.1', '1.2']},
{id: '1.1', name: 'one.one', childrenIds: ['1.1.1', '1.1.2']},
{id: '1.2', name: 'one.two'},
{id: '1.1.1', name: 'one.one.one'},
{id: '1.1.2', name: 'one.one.two', childrenIds: ['1.1.2.1', '1.1.2.2', '1.1.2.3']},
{id: '1.1.2.1', name: 'one.one.two.one'},
{id: '1.1.2.2', name: 'one.one.two.two'},
{id: '1.1.2.3', name: 'one.one.two.three'},
]
基本思想是递归调用expandComp
函数,而每个组件的children循环是使用提供的from
函数获得的通过 RxJS.
children 在 parent 组件中的分组是使用 RxJS 的 reduce
运算符提供的,在 expandComp
函数。
我最初尝试查看 RxJS 的 expand
运算符,但我无法找到使用它的解决方案。 @ggradnig 提出的解决方案利用 expand
.
我有一个 Firebase Firestore,其中 "Components" 作为根集合。集合中的每个文档("Component")可能有一个名为 "children" 的数组,数组中的每个项目都是另一个组件的 "id",本质上是一个 one-to-many 关系.由于每个 child 也是一个组件,它也可能有自己的 children 等等。
组件集合
Parent
1_Parent (document)
│ name: 'Parent'
│ id: '1_Parent'
└─children (array)
├─1.1_Child_one
└─1.2_Child_two
第一个Child
1.1_Child_one (document)
name: 'Child One'
id: '1.1_Child_one'
第二个Child
1.2_Child_two (document)
│ name: 'Child Two'
│ id: '1.2_Child_two'
└─children (array)
├─1.2.1_Grandchild_one
└─1.2.2_Grandchild_two
第一名child
1.2.1_Grandchild_one (document)
name: 'Grandchild One'
id: '1.2.1_Grandchild_one'
二等奖child
1.2.2_Grandchild_two (document)
name: 'Grandchild Two'
id: '1.2.2_Grandchild_two'
在我的代码中,我想为每个组件创建一个 object,如果它有一个 children 数组,那么数组中的每个 id 都被完全成熟的 object 从 Firestore 检索。
输出 object 树应该是这样的
1_Parent
│ name: 'Parent'
│ id: '1_Parent'
└─children
├─1.1_Child_one
│ name: 'Child One'
│ id: '1.1_Child_one'
└─1.2_Child_two
│ name: 'Child Two'
│ id: '1.2_Child_two'
└─children
├─1.2.1_grandhild_one
│ name: 'Grandchild One'
│ id: '1.2.1_grandhild_one'
└─1.2.2_grandhild_two
name: 'Grandchild Two'
id: '1.2.2_grandhild_two'
作为 JSON 的输出 object 应如下所示
{
"name": "Parent",
"id": "1_Parent",
"children": [
{
"name": "Child One",
"id": "1.1_Child_one"
},
{
"name": "Child Two",
"id": "1.2_Child_two",
"children": [
{
"name": "Grandchild One",
"id": "1.2.1_Grandchild_one"
},
{
"name": "Grandchild Two",
"id": "1.2.2_Grandchild_two"
}
]
}
]
}
很明显,我们在这里需要递归,但我完全不知道如何使用 RxJS 创建递归函数。如果允许这样做,我将不胜感激一些提示或示例代码。
请注意,我在 Angular 项目中使用它,并且我正在使用 AngularFire 访问 Firebase-Firestore。
RxJS 中的递归最好使用 expand
运算符来解决。您为它提供一个投影函数,该函数 returns 一个 Observable,在收到通知时,再次使用发出的值调用投影函数。只要您的内部 Observable 不发射 EMPTY
或 complete
,它就会这样做。
在这样做的同时,每个通知也会转发给 expand
的订阅者,这与传统的递归不同,传统的递归只会在最后得到结果。
来自 expand
上的官方文档:
Recursively projects each source value to an Observable which is merged in the output Observable.
让我们看看你的例子。如果没有 RxJS,如果我们有一个同步数据源给我们一个节点的每个子节点(我们称它为 getChildById
),该函数可能如下所示:
function createFamilyTree(node) {
node.children = node.childrenIds.map(childId =>
createFamilyTree(
getChildById(childId)
)
);
return node;
}
现在,我们将使用 expand
运算符将其转换为 RxJS:
parentDataSource$.pipe(
map(parent => ({node: parent})),
expand(({node}) => // expand is called for every node recursively
(i.e. the parent and each child, then their children and so on)
!node ? EMPTY : // if there is no node, end the recursion
from(node.childrenIds) // we'll convert the array of children
ids to an observable stream
.pipe(
mergeMap(childId => getChildById(childId) // and get the child for
the given id
.pipe(
map(child => ({node: child, parent: node}))) // map it, so we have
the reference to the
parent later on
)
)
),
// we'll get a bunch of notifications, but only want the "root",
that's why we use reduce:
reduce((acc, {node, parent}) =>
!parent ?
node : // if we have no parent, that's the root node and we return it
parent.children.push(node) && acc // otherwise, we'll add children
)
);
使用的运算符在RxJS官方文档页面有详细解释:from, expand, reduce
编辑:可以在此处找到上述代码的干净且经过测试的版本:https://stackblitz.com/edit/rxjs-vzxhqf?devtoolsheight=60
您可以按照这些思路寻找解决方案
// simulates an async data fetch from a remote db
function getComponent(id) {
return of(components.find(comp => comp.id === id)).pipe(delay(10));
}
function expandComp(component: Observable<any>) {
return component.pipe(
tap(d => console.log('s', d.name)),
mergeMap(component => {
if (component.childrenIds) {
return concat(
of(component).pipe(tap(comp => comp['children'] = [])),
from(component.childrenIds).pipe(
mergeMap(childId => expandComp(getComponent(childId)))
)
)
.pipe(
reduce((parent: any, child) => {
parent.children.push(child);
return parent;
})
)
}
else {
return of(component);
}
})
)
}
.subscribe(d => // do stuff, e.g. console.log(JSON.stringify(d, null, 3)))
我用下面的测试数据测试了上面的代码
const componentIds: Array<string> = [
'1',
'1.1.2'
]
const components: Array<any> = [
{id: '1', name: 'one', childrenIds: ['1.1', '1.2']},
{id: '1.1', name: 'one.one', childrenIds: ['1.1.1', '1.1.2']},
{id: '1.2', name: 'one.two'},
{id: '1.1.1', name: 'one.one.one'},
{id: '1.1.2', name: 'one.one.two', childrenIds: ['1.1.2.1', '1.1.2.2', '1.1.2.3']},
{id: '1.1.2.1', name: 'one.one.two.one'},
{id: '1.1.2.2', name: 'one.one.two.two'},
{id: '1.1.2.3', name: 'one.one.two.three'},
]
基本思想是递归调用expandComp
函数,而每个组件的children循环是使用提供的from
函数获得的通过 RxJS.
children 在 parent 组件中的分组是使用 RxJS 的 reduce
运算符提供的,在 expandComp
函数。
我最初尝试查看 RxJS 的 expand
运算符,但我无法找到使用它的解决方案。 @ggradnig 提出的解决方案利用 expand
.