如何在侧边栏中制作可折叠列表。

4

我正在尝试制作侧边栏的可折叠列表。点击时,我会更改“isOpen”状态,并根据此状态显示或隐藏子链接。问题是所有的子链接同时打开。

在这里检查沙盒:https://codesandbox.io/s/infallible-moore-h16g6

const Sidebar = ({ title, children, data, opened, ...attrs }) => {
  const [isOpen, setTriger] = useState(false);
  const handleClick = idx => {
    setTriger(!isOpen)
  };

  return (
    <SidebarUI>
      {data.map((item, idx) => {
        return typeof item.data === "string" ?
        <div key={idx} >{item.name}</div>:
          <Fragment key={idx}>
            <div onClick={() => handleClick(idx)}>{item.name}</div>
            { item.data.map((subs, ids) => {
              return <Test isOpen={isOpen} key={ids}>++++{subs.name}</Test>;
            })}
          </Fragment>
      })}
    </SidebarUI>
  );
};
4个回答

4
尝试使用已折叠元素的状态创建对象,像这样:
const Sidebar = ({ title, children, data, opened, ...attrs }) => {
  const [collapseElements, setCollapse] = useState({});

  const handleClick = idx => {
    const currentElements = Object.assign({}, collapseElements);

    setCollapse({ ...currentElements, [idx]: !collapseElements[idx] });
  };

  return (
    <SidebarUI>
      {data.map((item, idx) => {
        return typeof item.data === "string" ? (
          <div key={idx}>{item.name}</div>
        ) : (
          <Fragment key={idx}>
            <div onClick={() => handleClick(idx)}>{item.name}</div>

            {item.data.map((subs, ids) => {
              return (
                <Test isOpen={collapseElements[idx]} key={ids}>
                  ++++{subs.name}
                </Test>
              );
            })}
          </Fragment>
        );
      })}
    </SidebarUI>
  );
};
export default Sidebar;

请查看这个沙盒

如果有帮助,请告诉我。


谢谢,它有效。你能解释一下这个字符串吗?现在我没有机会console.log它。但我很好奇它是做什么的 setCollapse({ ...currentElements, [idx]: !collapseElements[idx] }); }; - Yerlan Yeszhanov
没问题。基本上它的作用是获取之前可折叠元素(...currentElements),并将被点击的元素及其新值(true/false)添加到相应的位置([idx]: !collapseElements[idx])中。在调用setCollapse之前尝试添加console.log({ ...currentElements, [idx]: !collapseElements[idx] });以记录状态对象。 - AxelJunes

1

编辑:

我制作了一个新的codesandbox,加入了一些过渡效果。现在打开和关闭都更加流畅。


检查 this codesandbox

现在它可以打开和关闭。

你需要做的是保持你点击的index,并且只有当它是相同的index时才显示子项。

我还添加了一种关闭和打开的方法。

这里是使用你的问题代码实现的。

const Sidebar = ({ title, children, data, opened, ...attrs }) => {
  const [openedIndex , setTriger] = useState(false);
  const handleClick = idx => {
    // this ternary makes it possible to open and close 
    setTriger(idx === openedIndex ? -1 : idx)
  };

  return (
    <SidebarUI>
      {data.map((item, idx) => {
        return typeof item.data === "string" ?
        <div key={idx} >{item.name}</div>:
          <Fragment key={idx}>
            <div onClick={() => handleClick(idx)}>{item.name}</div>
            {// here you check if the idx is the same as the opened one}
            {// before showing the data of the item}
            {idx === openedIndex && item.data.map((subs, ids) => {
              return <Test isOpen={true} key={ids}>++++{subs.name}</Test>;
            })}
          </Fragment>
      })}
    </SidebarUI>
  );
};

嗨@Vencovsky。我认为你的解决方案存在问题,即之前点击的元素不会保持打开状态。因为它是一个折叠效果,用户没有点击关闭它们,所以它们应该保持打开状态。 - AxelJunes
@AxelJunes,原帖并没有说明他想要什么,但这也很容易改变。我会保留我的答案,你也可以保留你的方式,这样原帖作者就有更多选择了。 - Vencovsky
是的,你说得对,在问题中没有指定。@Yerlan Yeszhanov,你能编辑问题并包含它吗? - AxelJunes

0

您可以不使用状态来解决这个问题。尝试更改为以下方式:

<Fragment key={idx}>
  <div class="sidebar-item" onClick={e => openSidebar(e)}>
    {item.name}
  </div>

  {item.data.map((subs, ids) => {
   return (
      <div className="sidebar-subitem" key={ids}>
        ++++{subs.name}
      </div>
    );
  })}
</Fragment>

点击切换类

 function openSidebar(e) {
    e.preventDefault();
    e.target.classList.toggle("open");
  }

添加 CSS
.sidebar-subitem {
  display: none;
}

.sidebar-item.open + .sidebar-subitem {
  display: block;
}

0

这当然不如@axeljunes好,但也可以工作,所以我维护了一个单独的切换ID列表,并基于此进行切换。 另外,这是我第一次使用钩子,所以请原谅我(欢迎纠正我)

const Sidebar = ({ title, children, data, opened, ...attrs }) => {
  //const [isOpen, setTriger] = useState(false);
  const [list, setList] = useState([]);

  const handleClick = idx => {
    //setTriger(!isOpen);
    if(!list.includes(idx))
      setList([...list,idx]);
    else{      
      const newList = list.filter(e => e!==idx);
      setList(newList);
    }

  };
  return (
    <SidebarUI>
      {data.map((item, idx) => {
        return typeof item.data === "string" ? (
          <div key={idx}>{item.name}MAIN</div>
        ) : (
          <Fragment key={idx}>
            <div onClick={() => handleClick(idx)}>{item.name}IN</div>

            {item.data.map((subs, ids) => {
              return (
                <Test isOpen={list.includes(idx)} key={ids}>
                  ++++{subs.name}SIDE
                </Test>
              );
            })}
          </Fragment>
        );
      })}
    </SidebarUI>
  );
};
export default Sidebar;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接