使用 RxJS 展平 objects 的嵌套数组

flatten a nested array of objects using RxJS

我们的数据结构是一个嵌套数组 objects

interface dtModel {
  id: number;
  permission: number;
  childs: dtModel[];
}

const data: dtModel[] = [
  {
    id: 1,
    permission: 2,
    childs: [
      {
        id: 2,
        permission: 1,
        childs: [
          {
            id: 3,
            permission: 3,
            childs: [],
          },
        ],
      },
    ],
  },
  {
    id: 4,
    permission: 1,
    childs: [
      {
        id: 5,
        permission: 2,
        childs: [
          {
            id: 6,
            permission: 3,
            childs: [
              {
                id: 7,
                permission: 1,
                childs: [],
              },
            ],
          },
        ],
      },
    ],
  },
];

我尝试在 RxJs 的帮助下创建一个像这样的扁平数组,但我还没有成功

[
  {id:1,permission:2},
  {id:2,permission:1},
  {id:3,permission:3},
  {id:4,permission:1},
  {id:5,permission:2},
  {id:6,permission:3},
  {id:7,permission:1}
]

我试图在 'reduce' 的帮助下做到这一点,但因为我孩子的 属性 并不总是具有相同的长度,所以我无法用这种方法做到这一点。

of(data)
  .pipe(
    map((items: dtModel[]) => {
      return items.reduce((res, curr) => {
        res.push({ id: curr.id, permission: curr.permission });
        return res;
      }, []);
    })
  )
  .subscribe(console.log);

StackBlitz

您需要进行树(=您的数据结构)遍历,并且对于每个节点,您都必须将 {id://some id,permission://some permission} 压入结果数组。

你不需要 rxjs。

需要递归

const source = [
  {
    id: 1,
    permission: 2,
    childs: [
      {
        id: 2,
        permission: 1,
        childs: [
          {
            id: 3,
            permission: 3,
            childs: [],
          },
        ],
      },
    ],
  },
  {
    id: 4,
    permission: 1,
    childs: [
      {
        id: 5,
        permission: 2,
        childs: [
          {
            id: 6,
            permission: 3,
            childs: [
              {
                id: 7,
                permission: 1,
                childs: [],
              },
            ],
          },
        ],
      },
    ],
  },
];

const result  = [];

const getAllItemsPerChildren = (item) => {
  result.push({ id: item.id, permission: item.permission });
  if (item.childs) {
    return item.childs.map((i) => getAllItemsPerChildren(i));
  }
};

source.forEach((i) => getAllItemsPerChildren(i));

console.log(result)

RXJS 解决方案

演示:https://stackblitz.com/edit/typescript-dlva1z?file=index.ts


const source$ = of({
  data: [],
  last: false,
  check: source,
}).pipe(
  expand((data) =>
    data.last
      ? EMPTY
      : of(data.check).pipe(
          map((currentItem) => {
            const childs = (currentItem as any).flatMap((i) => i?.childs || []);
            return {
              data: currentItem.map((i) => ({
                id: i.id,
                permissions: i.permission,
              })),
              last: !childs?.length,
              check: childs,
            };
          })
        )
  ),
  reduce((acc, items) => [...acc, ...(items as any).data], [])
);
source$.subscribe((data) => console.log('result ', data));

解释:

  • 使用 expand 运算符的主要思想 - 它允许我们创建递归。
  • 要停止递归需要returnEMPTY
  • of({data: [],last: false, check: source}) - 这是我们的商店
  • data 被分配以保留已解析的项目 - 将在 reduce 运算符中使用 - 连接整个数据
  • 一个 return 来自 expand 等于一个 emit
  • last 允许控制回溯,我们正在检查是否所有项目都已解析
  • check - 要解析的元素,初始值设置为整个数据

一个简化的 expand 版本,只需要确保每个项目都被发出并在最后用 map

做一些清理

工作示例:https://codesandbox.io/s/rxjs-playground-forked-vr0l6s?file=/src/index.js

from(data)
  .pipe(
    expand((obj) => {
      return obj.childs && obj.childs.length
        ? from(obj.childs)
        : EMPTY;
    }),
    map(obj=>({ id: obj.id, permission: obj.permission })),
    toArray()
  )
  .subscribe(console.log);

很好的文章解释了expand用法https://ncjamieson.com/understanding-expand/