我有一个Firebase Firestore,其中以“组件”作为根集合。集合中的每个文档(“组件”)可以具有一个称为“子项”的数组,该数组中的每个项目都是另一个组件的“ id”,本质上是一对多的关系。由于每个孩子也是一个组成部分,因此它也可能有自己的孩子,依此类推。
1_Parent (document)
│ name: 'Parent'
│ id: '1_Parent'
└─children (array)
├─1.1_Child_one
└─1.2_Child_two
1.1_Child_one (document)
name: 'Child One'
id: '1.1_Child_one'
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
1.2.1_Grandchild_one (document)
name: 'Grandchild One'
id: '1.2.1_Grandchild_one'
1.2.2_Grandchild_two (document)
name: 'Grandchild Two'
id: '1.2.2_Grandchild_two'
在我的代码中,我想为每个组件创建一个对象,如果它具有子数组,则数组中的每个id都将从从Firestore检索的完全成熟的对象替换。
输出对象树应如下图所示
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输出对象应如下所示
{
"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。
答案 0 :(得分:4)
使用expand
运算符可以最好地解决RxJS中的递归问题。您为其提供了一个投影函数,该函数返回一个Observable,在收到通知时,将使用发出的值再次调用该投影函数。只要您的内部Observable不发出EMPTY
或complete
,它就会执行此操作。
这样做的同时,每个通知也都转发给expand
的订阅者,这与传统的递归不同,在传统的递归中,您只能在最后获得结果。
摘自expand
上的官方文档:
将每个源值递归地投影到一个Observable中,该值将合并到输出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
答案 1 :(得分:3)
您可能会遵循以下思路
// 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 循环都是使用RxJS提供的from
函数获得的。
使用在reduce
函数中使用的RxJS的expandComp
运算符来提供 parent 组件中 children 的分组。>
我起初尝试查看RxJS的expand
运算符,但无法找到使用它的解决方案。 @ggradnig提出的解决方案利用了expand
。