可折叠的手风琴

渲染一个带有多个可折叠内容元素的手风琴菜单。

  • 定义一个 AccordionItem 组件,它渲染一个 <button>。该按钮通过 handleClick 回调函数更新组件并通知其父组件。
  • AccordionItem 中使用 isCollapsed 属性来确定其外观并设置其 className
  • 定义一个 Accordion 组件。使用 useState() 钩子将 bindIndex 状态变量的初始值设置为 defaultIndex
  • 通过识别函数的名称,过滤 children 来删除除 AccordionItem 之外的不必要的节点。
  • 对收集到的节点使用 Array.prototype.map() 来渲染各个可折叠元素。
  • 定义 changeItem,当点击 AccordionItem<button> 时将执行该函数。
  • changeItem 执行传递的回调函数 onItemClick,并根据点击的元素更新 bindIndex
.accordion-item.collapsed {
  display: none;
}

.accordion-item.expanded {
  display: block;
}

.accordion-button {
  display: block;
  width: 100%;
}
const AccordionItem = ({ label, isCollapsed, handleClick, children }) => {
  return (
    <>
      <button className="accordion-button" onClick={handleClick}>
        {label}
      </button>
      <div
        className={`accordion-item ${isCollapsed ? 'collapsed' : 'expanded'}`}
        aria-expanded={isCollapsed}
      >
        {children}
      </div>
    </>
  );
};

const Accordion = ({ defaultIndex, onItemClick, children }) => {
  const [bindIndex, setBindIndex] = React.useState(defaultIndex);

  const changeItem = itemIndex => {
    if (typeof onItemClick === 'function') onItemClick(itemIndex);
    if (itemIndex !== bindIndex) setBindIndex(itemIndex);
  };
  const items = children.filter(item => item.type.name === 'AccordionItem');

  return (
    <>
      {items.map(({ props }) => (
        <AccordionItem
          isCollapsed={bindIndex !== props.index}
          label={props.label}
          handleClick={() => changeItem(props.index)}
          children={props.children}
        />
      ))}
    </>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <Accordion defaultIndex="1" onItemClick={console.log}>
    <AccordionItem label="A" index="1">
      Lorem ipsum
    </AccordionItem>
    <AccordionItem label="B" index="2">
      Dolor sit amet
    </AccordionItem>
  </Accordion>
);

ReactDOM.createRoot(document.getElementById('root')).render( Lorem ipsum Dolor sit amet );