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

如何根据动态内容正确更新React函数式Accordion组件的MaxHeight

解决React手风琴组件动态内容高度不更新的问题

你遇到的这个问题非常典型——当手风琴的内容从父组件传入且动态变化时,DOM渲染完成的时机和我们计算高度的时机不匹配,导致无法获取到正确的scrollHeight。下面给你几个实用的解决方案,按推荐程度排序:

1. 使用ResizeObserver监听内容容器尺寸变化

这是最健壮的方案,ResizeObserver是浏览器原生API,能直接监听元素的尺寸变化,不管内容是因为父组件传值变化、内部状态更新还是异步加载(比如图片加载完成)导致的高度改变,都能准确捕捉到。

修改你的组件代码:

import React, { useEffect, useRef, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronUp, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import './Accordion.css';

const Accordion = ({ title, content, active }) => {
  const [isActive, setIsActive] = useState(active);
  const [expanderStyle, setExpanderstyle] = useState({ maxHeight: 0 });
  const contentDiv = useRef();

  // 同步父组件的active状态
  useEffect(() => {
    setIsActive(active);
  }, [active]);

  // 监听内容容器的尺寸变化
  useEffect(() => {
    const updateHeight = () => {
      if (contentDiv.current && isActive) {
        setExpanderstyle({ maxHeight: contentDiv.current.scrollHeight + 1 });
      }
    };

    // 初始化时先计算一次
    updateHeight();

    // 创建ResizeObserver实例
    const observer = new ResizeObserver(() => {
      if (isActive) {
        updateHeight();
      }
    });

    if (contentDiv.current) {
      observer.observe(contentDiv.current);
    }

    // 组件卸载时清理监听
    return () => {
      if (contentDiv.current) {
        observer.unobserve(contentDiv.current);
      }
    };
  }, [isActive]);

  const toggle = () => {
    setIsActive(!isActive);
  };

  return (
    <div className="accordion">
      <div className="accordion-title">
        <div>
          <button className="btn btn-icon round small" onClick={toggle}>
            <FontAwesomeIcon icon={faChevronUp} className={`icon rotator ${isActive ? "active" : ""}`} />
          </button>
        </div>
        <div className="accordion-title--title">
          {title}
        </div>
      </div>
      <div ref={contentDiv} className="accordion-content" style={expanderStyle}>
        {content}
      </div>
    </div>
  );
};

export default Accordion;

这个方案的优势是无需依赖content的引用变化,哪怕内容是同一个React元素但内部DOM更新(比如子组件状态变化),也能正确触发高度计算。

2. 结合requestAnimationFrame和content依赖

如果你的内容变化总是伴随content prop的引用变化(比如父组件每次传新的JSX元素),可以在监听content变化时,用requestAnimationFrame确保DOM已经渲染完成后再计算高度:

useEffect(() => {
  const updateHeight = () => {
    if (contentDiv.current) {
      setExpanderstyle(isActive ? { maxHeight: contentDiv.current.scrollHeight + 1 } : { maxHeight: 0 });
    }
  };

  // 用requestAnimationFrame等待DOM更新完成
  const rafId = requestAnimationFrame(updateHeight);
  return () => cancelAnimationFrame(rafId);
}, [isActive, content]); // 加入content作为依赖项

注意:如果content是同一个引用(比如父组件的状态变化但没有生成新的JSX),这个方案不会触发,这是它的局限性。

3. 使用useLayoutEffect替代useEffect

useLayoutEffect会在DOM更新完成后同步执行,比useEffect的时机更早,能确保我们获取到最新的DOM尺寸:

useLayoutEffect(() => {
  if (contentDiv.current) {
    setExpanderstyle(isActive ? { maxHeight: contentDiv.current.scrollHeight + 1 } : { maxHeight: 0 });
  }
}, [isActive, content]);

不过要注意,useLayoutEffect会阻塞浏览器绘制,如果计算逻辑复杂可能影响性能,所以优先推荐前两个方案。

额外优化:同步父组件的active状态

你原来的代码中,父组件传入的active prop只会在组件初始化时设置isActive状态,如果父组件后续修改active,子组件的状态不会同步。上面的方案已经加入了同步逻辑,确保父组件能控制手风琴的展开/收起状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 18:22:35