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

NextJS TypeScript手风琴组件实现多开不关闭其他项

问题原因

当前实现用单个数字类型的state存储激活的手风琴项id,同一时间只能记录一个激活项,点击新项时会直接覆盖之前的激活状态,因此只能同时展开一项。

实现方案

将存储激活状态的state从单个数字改为存储所有已展开项id的数组,切换状态时仅针对当前点击的项做增删操作,完全不影响其他已展开项的状态,即可实现同时展开任意数量手风琴项的效果。

修改步骤

  • 调整useState初始值为空数组,用于存放所有当前处于展开状态的手风琴项索引
  • 重写toggleAccordion切换逻辑:点击项时如果对应id已存在于激活数组中,就将该id从数组移除(触发收起);如果id不在数组中,就将id追加到数组中(触发展开)
  • 将模板中所有判断accordion === id的逻辑,替换为判断激活数组是否包含当前项id

修改后完整代码

import React, { useState } from 'react'
import { FaChevronDown, FaChevronUp } from 'react-icons/fa';

const FaqList = () => {
  const faqArr = [
    { id: 1, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
    { id: 2, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
    { id: 3, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
    { id: 4, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
    { id: 5, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
    { id: 6, faqQuestion: 'Lorem Ipsum Dolor Sit Amet?', answer: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus. Maecenas eget condimentum velit, sit amet feugiat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.' },
  ];
  
  // 存储所有展开项的id数组,初始为空即默认全部收起
  const [activeIds, setActiveIds] = useState<number[]>([]);

  const toggleAccordion = (id: number) => {
    setActiveIds(prev => 
      prev.includes(id) 
        // 已展开则过滤掉当前id,实现独立收起
        ? prev.filter(item => item !== id)
        // 未展开则追加当前id,不改动其他已展开项状态
        : [...prev, id]
    )
  }

  return (
    <>
      <div className='grid grid-cols-1 xl:grid-cols-2'>
        {
          faqArr.map((FAQ, id) => {
            const isOpen = activeIds.includes(id);
            return (
              <div key={id} onClick={() => toggleAccordion(id)} className='gap-12 mt-2 mb-2 cursor-pointer ease-in'>
                <div className={isOpen ? 'block' : ''}>
                  <div className='flex justify-between pt-4 pr-6 pb-4 pl-6 border-2 m-2 rounded-xl text-black dark:text-white bg-white dark:bg-black relative z-auto dark:border-white'>
                    <h6 className="self-center font-bold">{FAQ.faqQuestion}</h6>
                      { isOpen ? (
                        <FaChevronUp className='text-k-dGray dark:text-k-lGray self-center'  />
                      ) : (
                        <FaChevronDown className='text-k-dGray dark:text-k-lGray self-center' />
                      ) }
                  </div>
                </div>
                <div className={isOpen ? 'block ease-in' : 'hidden ease-in'}>
                  <p className='flex pt-8 pr-6 pb-4 pl-6 border-l-2 border-r-2 border-b-2 m-2 -mt-6 rounded-xl text-k-dGray dark:text-k-lGray z-0 rounded-tl-none rounded-tr-none border-[666]'>{FAQ.answer}</p>
                </div>
              </div>
            )
          })
        }
      </div>
    </>
  )
}

export default FaqList

额外优化点

  • 补全了TypeScript类型标注,匹配TS项目的类型校验要求
  • 将原组件名从小驼峰格式的faqList改为大驼峰格式的FaqList,符合React组件命名规范
  • 提前提取isOpen布尔变量,避免模板中重复编写相同判断逻辑,代码可读性更高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:39:21