如何在 React 中创建动态嵌套手风琴

How to create dynamic nested accordion in React

我如何制作这样的手风琴

-parent
   -subparent1
      -subparent2
        ...
          -subparentN
             - child

这是我的数据。

//parents
{id: 1, name: "", parent_id: null}
{id: 2, name: "", parent_id: null }
{id: 3, name: "", parent_id: null }
{id: 4, name: "", parent_id: null}

//children
{id: 5, name: "", parent_id: 1}
{id: 6, name: "", parent_id: 1}
{id: 7, name: "", parent_id: 5}
{id: 8, name: "", parent_id: 5}
{id: 9, name: "", parent_id: 6}
{id: 10, name: "", parent_id: 6}
{id: 11, name: "", parent_id: 6}
{id: 12, name: "", parent_id: 6}
{id: 13,name: "", parent_id: 6}
{id: 14, name: "", parent_id: 6}

基本上有parent_id:null的人都是parents,当我点击他们的时候我想让他们的潜力children显示出来,如果他们有的话,现在这个不是太难了,但我不明白的是如何显示子父母的 children

我认为你的数据结构有缺陷。除了 child 到 parent 的关系,您还应该跟踪 parent 到 child 的关系。现在您将能够轻松地遍历数据并呈现子 parent 的 children.

{id: 1, parent_id: null, children: [
  {id: 2, parent_id: 1, children: []},
  {id: 3, parent_id: 1, children: [
    {id: 4, parent_id: 3, children: []}
  ]}
]}

如果您需要保持所有 objects 内联,您可以像这样构建数据:

{id: 1, parent_id: null, children: [2, 3]}
{id: 2, parent_id: 1, children: []},
{id: 3, parent_id: 1, children: [4]},
{id: 4, parent_id: 3, children: []}

您可以遍历所有项目的列表并将每个子项目添加到它们的父项。之后,您只需遍历数组中的所有项目并创建它们各自的 html.

const items = [
  //parents
  {id: 1, name: "1", parent_id: null},
  {id: 2, name: "2", parent_id: null },
  {id: 3, name: "3", parent_id: null },
  {id: 4, name: "4", parent_id: null},

  //children
  {id: 5, name: "5", parent_id: 1},
  {id: 6, name: "6", parent_id: 1},
  {id: 7, name: "7", parent_id: 5},
  {id: 8, name: "8", parent_id: 5},
  {id: 9, name: "9", parent_id: 6},
  {id: 10, name: "10", parent_id: 6},
  {id: 11, name: "11", parent_id: 6},
  {id: 12, name: "12", parent_id: 6},
  {id: 13,name: "13", parent_id: 6},
  {id: 14, name: "14", parent_id: 6},
];

for(const item of items) {
  // Find the parent object
  const parent = items.find(({ id }) => id === item.parent_id);
  // If the parent is found add the object to its children array
  if(parent) {
    parent.children = parent.children ? [...parent.children, item] : [item]
  }
};

// Only keep root elements (parents) in the main array
const list = items.filter(({ parent_id }) => !parent_id);

// console.log(list);

// Show tree (vanillaJS, no REACT)
for(const item of list) {
  // Create a new branch for each item
  const ul = createBranch(item);
  // Append branch to the document
  document.body.appendChild(ul);
}

function createBranch(item) {
  // Create ul item for each branch
  const ul = document.createElement("ul");
  // Add current item as li to the branch
  const li = document.createElement("li");
  li.textContent = item.name;
  ul.appendChild(li);

  // Check if there are children
  if(item.children) {
    // Create a new branch for each child
    for(const child of item.children) {
      const subUl = createBranch(child);
      // Append child branch to current branch
      ul.appendChild(subUl);
    }
  }
  
  return ul;
}
ul {
  margin: 0;
  padding-left: 2rem;
}

我认为您的数据结构应该是一个嵌套对象,类似于您看到菜单的工作方式,即

[
    {id: 1, name:"", children: [
      {id: 5, name: "", children: []},
      {id: 6, name: "", children: [
        {id: 7, name: "", children: []},
        {id: 8, name: "", children: []},
      ]},
    ]},
    {id: 2, name:"", children:[]}
]

那么你需要一个函数来输出每一项:

const returnMenuItem = (item, i) =>{
  let menuItem;

  if (item.children.length===0) {
    menuItem = <div key={i}>{item.label}</div>;
  }
  else {
    let menuItemChildren = item.children.map((item,i)=>{
      let menuItem = returnMenuItem(item,i);
      return menuItem;
    });
    menuItem = <div key={i}>
      <div>{item.label}</div>
      <div>
        {menuItemChildren}
      </div>
    </div>;
  }
  return menuItem;
}

您将通过遍历项目来调用此函数:

let menuItems = data.map((item,i)=>{
    let menuItem = returnMenuItem(item,i);
    return menuItem;
});

完整的组件如下所示:

import React, { useState, useEffect } from "react";
import { UncontrolledCollapse } from "reactstrap";

const Menu = (props) => {

  const [loading, setLoading] = useState(true);
  const [items, setItems] = useState([]);

  useEffect(() => {
    const menuData = [
      {
        id: 1,
        name: "test 1",
        children: [
          { id: 5, name: "test 5", children: [] },
          {
            id: 6,
            name: "test 6",
            children: [
              { id: 7, name: "test 7", children: [] },
              { id: 8, name: "test 8", children: [] }
            ]
          }
        ]
      },
      { id: 2, name: "test 2", children: [] }
    ];
    const returnMenuItem = (item, i) => {
      let menuItem;

      if (item.children.length === 0) {
        menuItem = (
          <div className="item" key={i}>
            {item.name}
          </div>
        );
      } else {
        let menuItemChildren = item.children.map((item, i) => {
          let menuItem = returnMenuItem(item, i);
          return menuItem;
        });
        menuItem = (
          <div key={i} className="item">
            <div className="toggler" id={`toggle-menu-item-${item.id}`}>
              {item.name}
            </div>
            <UncontrolledCollapse
              className="children"
              toggler={`#toggle-menu-item-${item.id}`}
            >
              {menuItemChildren}
            </UncontrolledCollapse>
          </div>
        );
      }
      return menuItem;
    };

    const load = async () => {
      setLoading(false);
      let menuItems = menuData.map((item, i) => {
        let menuItem = returnMenuItem(item, i);
        return menuItem;
      });
      setItems(menuItems);
    };
    if (loading) {
      load();
    }
  }, [loading]);

  return <div className="items">{items}</div>;
};
export default Menu;

并且至少 css:

.item {
  display: block;
}
.item > .children {
  padding: 0 0 0 40px;
}
.item > .toggler {
  display: inline-block;
}

.item::before {
  content: "-";
  padding: 0 5px 0 0;
}

您可以在此处找到可用的代码沙箱 sandbox