You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为Next.js+TypeScript的Accordion组件添加嵌套功能

实现嵌套式Next.js + TypeScript Accordion组件

以下是针对你的两个技术挑战的解决方案:


1. TypeScript递归类型定义修改

首先需要在接口中定义递归的数据结构,让accordionItems的元素支持嵌套的children字段,同时兼容单个子项或子项数组的场景:

// IAccordion.ts
import React from 'react';

// 递归定义Accordion项的数据结构
export interface IAccordionItemData {
  id: string | number;
  title: string | React.ReactElement;
  content: string | React.ReactElement;
  children?: IAccordionItemData | IAccordionItemData[]; // 支持单个子项或子项数组
}

export interface IAccordion {
  accordionItems: IAccordionItemData[];
  isMultiple?: boolean;
  className?: string;
  itemClassName?: string;
}

export interface IAccordionItem
  extends Pick<IAccordion, 'isMultiple' | 'itemClassName'> {
  accordionItem: IAccordionItemData;
  isActive: boolean;
  toggleOnlyOneAccordionItemHandler: (id: IAccordionItemData['id']) => void;
}

2. 递归组件实现修改

在AccordionItem组件中,判断当前项是否存在children,如果有则递归渲染Accordion组件,同时处理单个子项转数组的逻辑,并添加嵌套样式区分层级:

修改后的Accordion组件代码

// Accordion.tsx
import React, { useEffect, useState } from 'react';
import classes from './Accordion.module.scss';
import { IAccordion, IAccordionItem, IAccordionItemData } from './IAccordion';

const AccordionItem: React.FC<IAccordionItem> = ({
  accordionItem,
  isMultiple,
  isActive = false,
  toggleOnlyOneAccordionItemHandler,
  itemClassName,
}) => {
  const [isVisible, setIsVisible] = useState(isActive);

  useEffect(() => {
    setIsVisible(isActive);
  }, [isActive]);

  const toggleAccordionItemHandler = () => {
    setIsVisible(prevState => !prevState);
  };

  // 统一将children转换为数组,兼容单个子项或数组的情况
  const getChildrenItems = (): IAccordionItemData[] => {
    if (!accordionItem.children) return [];
    return Array.isArray(accordionItem.children) ? accordionItem.children : [accordionItem.children];
  };

  return (
    <div className={`${classes.item} ${itemClassName}`}>
      <div
        className={classes.title}
        onClick={() =>
          isMultiple
            ? toggleAccordionItemHandler()
            : toggleOnlyOneAccordionItemHandler(accordionItem.id)
        }
      >
        <h2>{accordionItem.title}</h2>
        <span>{isVisible ? '-' : '+'}</span>
      </div>
      <div
        className={`${classes.content} ${isVisible ? classes.show : ''}`}
      >
        {accordionItem.content}
        {/* 递归渲染嵌套Accordion,仅在当前项展开时显示 */}
        {isVisible && getChildrenItems().length > 0 && (
          <Accordion
            accordionItems={getChildrenItems()}
            isMultiple={isMultiple}
            className={classes['nested-accordion']}
            itemClassName={itemClassName}
          />
        )}
      </div>
    </div>
  );
};

export const Accordion: React.FC<IAccordion> = ({
  accordionItems,
  isMultiple = true,
  className,
  itemClassName,
}) => {
  const [selectedAccordionItemId, setSelectedAccordionItemId] = useState<null | number | string>(null);

  const toggleOnlyOneAccordionItemHandler = (accordionItemId: number | string) => {
    setSelectedAccordionItemId(prevId => prevId === accordionItemId ? null : accordionItemId);
  };

  return (
    <div className={`${classes.accordion} ${className}`}>
      {accordionItems.map(accordionItem => (
        <React.Fragment key={accordionItem.id}>
          <AccordionItem
            accordionItem={accordionItem}
            isMultiple={isMultiple}
            itemClassName={itemClassName}
            isActive={selectedAccordionItemId === accordionItem.id}
            toggleOnlyOneAccordionItemHandler={toggleOnlyOneAccordionItemHandler}
          />
        </React.Fragment>
      ))}
    </div>
  );
};

嵌套样式调整

在样式文件中添加嵌套Accordion的样式,让层级更清晰:

/* Accordion.module.scss */
.accordion{
  width: 500px;
}

/* 嵌套Accordion的缩进和宽度调整 */
.accordion.nested-accordion {
  width: calc(100% - 20px);
  margin: 10px 0 0 20px;
}

.item{
  background: #f0ebe1;
  margin-bottom: 5px;
}

.title{
  color: #85662b;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  padding: 10px 20px;
}

.content{
  color: #8b7f75;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.5s cubic-bezier(0,1,0,1);
}

.content.show{
  height: auto;
  max-height: 9999px;
  transition: max-height 0.5s cubic-bezier(1,0,1,0);
  padding: 10px 20px;
}

使用示例

现在可以按照你期望的结构使用组件,支持任意层级的嵌套:

<Accordion
  accordionItems={[
    {
      title: 'Question1',
      id: 'Question1',
      content:
        'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque dignissimos dolore earum e' +
        'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque dignissimos dolore earum e Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque dignissimos dolore earum e' +
        ' Cumque dignissimos dolore earum explicabo incidunt omnis pariatur repellat sapiente soluta voluptate?',
      children: {
        title: 'Question1.1',
        id: 'Question1.1',
        content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque dignissimos dolore earum e',
        children: {
          title: 'Question1.1.1',
          id: 'Question1.1.1',
          content: 'Lorem simos dolore earum e',
        }
      }
    },
    {
      title: 'Question2',
      id: 'Question2',
      content:
        'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab accusantium ad animi aspernatur aut beatae culpa eius esse expedita fugit hic ipsa laboriosam laborum maxime optio, quis sint tempora ullam vitae voluptatum. Eum, facere, possimus!',
    },
  ]}
/>

内容的提问来源于stack exchange,提问作者Ali Ehyaie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 00:48:20