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

React+Vite环境下折叠面板状态不更新异常问题

问题分析与修复方案

核心问题

  1. DOM获取时机错误:组件初始化时直接调用document.querySelectorAll(".collapseable"),此时组件还未渲染到DOM,得到的是空数组,导致状态数组长度为0,无法正确初始化面板收起状态,所以刷新后面板默认展开。
  2. 状态逻辑不符合需求:原handleCollapse仅切换当前面板的状态,其他面板状态保持,无法实现“同一时间仅一个面板打开”的要求;且当状态数组长度异常时,点击会导致所有面板状态混乱。
  3. 直接操作DOM不符合React范式:通过document.querySelector和手动修改style.height的方式,容易和React的状态更新机制冲突,导致交互异常。

修复后的代码

import React, { useState, useRef, useEffect } from "react";
import {
  facebookIcon,
  foursquareIcon,
  instagramIcon,
  lvLogo,
  pinterestIcon,
  plusIcon,
  snapchatIcon,
  twitterIcon,
  youtubeIcon,
} from "../utils";

const FooterTop = () => {
  // 用单个状态存储当前打开的面板索引,null表示全关闭
  const [openIndex, setOpenIndex] = useState(null);
  // 用ref存储所有面板元素,避免频繁查询DOM
  const collapseRefs = useRef([]);

  // 监听openIndex变化,更新面板高度
  useEffect(() => {
    collapseRefs.current.forEach((panel, index) => {
      if (!panel) return;
      if (index === openIndex) {
        // 展开:设置为内容高度
        panel.style.height = `${panel.scrollHeight}px`;
      } else {
        // 收起:设置为0
        panel.style.height = "0px";
      }
    });
  }, [openIndex]);

  const handleCollapse = (index) => {
    // 点击已打开的面板则关闭,否则打开该面板并关闭其他
    setOpenIndex(prev => prev === index ? null : index);
  };

  return (
    <>
      <div className="border-b">
        <div className="mt-8 flex justify-center">
          <img width={200} src={lvLogo} alt="LV Logo" />
        </div>
        <div className="mt-8">
          <div className="block border-t mx-5 py-4">
            <button
              onClick={() => handleCollapse(0)}
              className="flex items-center justify-between w-full"
            >
              <div>Wünschen Sie Beratung?</div>
              <img width={10} src={plusIcon} alt="add" />
            </button>
            <div
              ref={el => collapseRefs.current[0] = el}
              className="collapseable overflow-hidden transition-all duration-300"
            >
              <ul className="pl-5">
                <li className="my-10">
                  <div>
                    Kontaktieren Sie unseren Kundenservice gerne telefonisch
                    unter{" "}
                    <a className="underlined-link" href="#">
                      +49 (0) 211 864 700
                    </a>
                    , per{" "}
                    <a className="underlined-link" href="#">
                      E-mail
                    </a>{" "}
                    oder{" "}
                    <a className="underlined-link" href="#">
                      Whatsapp
                    </a>
                    .
                  </div>
                </li>
                <li className="my-10">
                  <a href="#">Häufige Frage (FAQ)</a>
                </li>
                <li className="my-10">
                  <a href="#">Reparatur</a>
                </li>
                <li className="my-5">
                  <a href="#">Geschäfte</a>
                </li>
              </ul>
            </div>
          </div>
          <div className="block border-t mx-5 py-4">
            <button
              onClick={() => handleCollapse(1)}
              className="flex items-center justify-between w-full"
            >
              <div>Services</div>
              <img width={10} src={plusIcon} alt="add" />
            </button>
            <div
              ref={el => collapseRefs.current[1] = el}
              className="collapseable overflow-hidden transition-all duration-300"
            >
              <ul className="pl-5">
                <li className="my-10">
                  <a href="#">Care Service</a>
                </li>
                <li className="my-10">
                  <a href="#">Personalisierung</a>
                </li>
                <li className="my-5">
                  <a href="#">Die Kunst des Schenkens</a>
                </li>
              </ul>
            </div>
          </div>
          <div className="block border-t mx-5 py-4">
            <button
              onClick={() => handleCollapse(2)}
              className="flex items-center justify-between w-full"
            >
              <div>Über Louis Vuitton</div>
              <img width={10} src={plusIcon} alt="add" />
            </button>
            <div
              ref={el => collapseRefs.current[2] = el}
              className="collapseable overflow-hidden transition-all duration-300"
            >
              <ul className="pl-5">
                <li className="my-10">
                  <a href="#">Fashion Shows</a>
                </li>
                <li className="my-10">
                  <a href="#">Kunst & Kultur</a>
                </li>
                <li className="my-10">
                  <a href="#">Maison Louis Vuitton</a>
                </li>
                <li className="my-10">
                  <a href="#">Nachhaltigkeit</a>
                </li>
                <li className="my-10">
                  <a href="#">Neuigkeiten</a>
                </li>
                <li className="my-10">
                  <a href="#">Karriere</a>
                </li>
                <li className="my-5">
                  <a href="#">Fondation Louis Vuitton</a>
                </li>
              </ul>
            </div>
          </div>
          <div className="block border-t mx-5 py-4">
            <button
              onClick={() => handleCollapse(3)}
              className="flex items-center justify-between w-full"
            >
              <div>Folgen Sie uns</div>
              <img width={10} src={plusIcon} alt="add" />
            </button>
            <div
              ref={el => collapseRefs.current[3] = el}
              className="collapseable overflow-hidden transition-all duration-300"
            >
              <ul className="pl-5">
                <li className="my-10">
                  Abbonieren Sie den{" "}
                  <a className="underlined-link" href="#">
                    Newsletter von Louis Vuitton
                  </a>{" "}
                  und erfahren Sie zuerst von unseren neuesten Kollektionen,
                  Kampagnen und Initiativen.
                </li>
                <li className="my-10">
                  <a href="#">Apps</a>
                </li>
                <li className="flex my-10 justify-center gap-7">
                  <a href="#">
                    <img width={25} src={instagramIcon} alt="Instagram" />
                  </a>
                  <a href="#">
                    <img width={25} src={facebookIcon} alt="Facebook" />
                  </a>
                  <a href="#">
                    <img width={25} src={twitterIcon} alt="X" />
                  </a>
                  <a href="#">
                    <img width={25} src={youtubeIcon} alt="YouTube" />
                  </a>
                  <a href="#">
                    <img width={25} src={snapchatIcon} alt="Snapchat" />
                  </a>
                  <a href="#">
                    <img width={25} src={pinterestIcon} alt="Pinterest" />
                  </a>
                  <a href="#">
                    <img width={25} src={foursquareIcon} alt="Foursquare" />
                  </a>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default FooterTop;

关键优化点

  • 状态简化:用openIndex单个状态替代数组,更直观地控制唯一打开的面板,初始值null确保所有面板默认收起。
  • 正确获取DOM元素:使用useRef存储每个面板元素,避免在组件初始化时查询未渲染的DOM,同时减少DOM查询次数。
  • 交互逻辑修正:点击按钮时,若当前面板已打开则关闭,否则打开该面板并自动关闭其他面板,完全符合“同一时间仅一个面板打开”的需求。
  • 符合React范式:通过状态驱动DOM样式变化,避免直接操作DOM导致的状态不一致问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:33:10