解析数据到可映射的嵌套列表反应

时间:2020-02-16 21:54:06

标签: javascript reactjs algorithm sorting

我正在建立一个数据库,您可以在其中添加对会议的评论以及对这些会议的答复。我从this article获得了启发,因为我之前从未做过类似的事情。

我正在从数据库中获取评论,其结构如下所示:

"comments": [
        {
          "id": 5,
          "content": "First comment",
          "path": [
            5
          ],
          "depth": 1
        },
        {
          "id": 6,
          "content": "First reply on first comment",
          "path": [
            5,
            6
          ],
          "depth": 2
        },
        {
          "id": 7,
          "content": "Second comment",
          "path": [
            7
          ],
          "depth": 1
        },
        {
          "id": 8,
          "content": "First reply on second comment",
          "path": [
            7,
            8
          ],
          "depth": 2
        },
        {
          "id": 9,
          "content": "Second reply on second comment",
          "path": [
            7,
            9
          ],
          "depth": 2
        },
        {
          "id": 10,
          "content": "First reply on second comment second reply",
          "path": [
            7,
            9,
            10
          ],
          "depth": 3
        }
      ]

当前,我正在尝试使用React应用程序创建一个嵌套列表,并以类似于StackOverflow的方式显示注释。我对结构的外观有所了解,但我不太确定如何实现它。

comments: [
    {
        id: 5,
        content: "First comment",
        replies: [
            {
                id: 6,
                content: "First reply on first comment",
                depth: 2                               
            }
        ]
    },
    {
        id: 100,
        content: "Comment with no replies",
        replies: []
    }

]

在此方面的任何帮助都深表感谢,我希望我已经清楚地描述了我的问题。

感谢阅读,祝您愉快。

1 个答案:

答案 0 :(得分:0)

我要做的第一件事是将parentIdchildrenorder添加到每个项目。无论您从哪个节点开始,这都可以使您上下遍历树并确保正确的排序。 parentId是不言自明的,而children应该被命名为childrenIds,因为它只是以适当的顺序保存了孩子的ID。

然后,我通常喜欢将数组转换为以某些唯一值(例如ID)作为键的映射。这使您可以执行O(1)操作,以转到可能会被编辑或删除的任何节点。我喜欢将Ramda用于此类事情,但是您可以使用任何其他实用程序或编写自己的实用程序。

utils.js

/**
 * Convert an array to an object, using the `id` as the key.
 */
 const keyById = R.indexBy(R.prop("id"));

现在,您需要找到列表顶部的注释,或者如果有注释,则在“根”注释下面。由于我选择使用parentId: null来表示这一点,因此我们可以在其中找到这些顶级项。

const getTopLevelParents = R.filter(x => !x.parentId);

这样,我们可以轻松地获取顶级项目以开始迭代。这可能会使它变得更加复杂。我也在这里使用Ramda,所以我希望它对于理解不是很不寻常。要点是,从topLevel项中的第一个项开始,我们递归地找到子项,按“顺序”排序,设置深度,并将该项数组连接为单个数组。因此,结果是线性的并且排序正确。因为当我们将数据映射到组件时,我们不想为了找到父母和孩子而跳过。我们已经想通了所有这些,希望能够从上到下。

/**
 * Converts the mapping to an array and reorders the children so they are directly adjacent with the depth set.
 * @param treeMap Key value pair where the key is the ID and the value is the chapter
 */
function getTreeMapAsList(treeMap) {
  const topLevel = R.compose(
    getTopLevelParents,
    R.values
  )(treeMap);

  /**
   * Nest this function so it can have access to treeMap.
   */
  function getTree(data, depth = 0) {
    return R.reduce(
      (result, curr) => {
        result.push(curr);
        // hasLength is a utility that just checks if the length of an     
        // item is greater than 0 in a way that won't blow up if something
        // doesn't exist.  
        if (hasLength(R.prop("children", curr))) {
          const children = R.compose(
            R.sortBy(R.prop("order")),
            R.reduce((result, id) => {
              const child = R.prop(id, treeMap);
              if (child) {
                return R.compose(
                  R.append(R.__, result),
                  R.assoc("depth", depth + 1)
                )(child);
              }
              return result;
            }, [])
          )(curr.children);

          return result.concat(getTree(children, depth + 1));
        }
        return result;
      },
      [],
      data
    );
  }

树现在已正确排序,具有适当的深度,并已展平为一个数组。最后,您可以使用注释组件在此数组上进行映射。

Comment.js

function Comment({ data }) {
  return (
    <div
      style={{
        //It's important to default to zero here. You could also
        // just do this in the data or the mapper originally.
        marginLeft: `${R.propOr(0, "depth", data)}rem`
      }}
    >
      {data.content}
    </div>
  );
}

App.js

function App() {
  // This is the map and should be stored in state somewhere.
  // If it's changing a lot, you should memoize it.
  const treeMap = keyById(data.comments);

  // This is the array, that is now ordered with appropriate depths
  // This should also be memoized.
  const treeList = getTreeMapAsList(treeMap);

  return (
    <div>
      <div>
        {/* Now we just have an array to iterate over. */}
        {treeList.map(item => {
          return <Comment data={item} key={item.id} />;
        })}
      </div>
    </div>
  );
}

Here's a working example。祝你好运!