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

如何在React中获取SelectedCard渲染完成后的准确高度?

问题描述

我需要在SelectedCard组件完全渲染完成后获取其高度,最多会渲染3个该组件,最终取它们的高度最大值。组件渲染过程中会显示骨架屏,但目前获取的height值不稳定——单组件测试时正确高度应为160,但偶尔会拿到39或85,应该是捕获了骨架屏或渲染中的高度。

试过useEffect,结果一样;在useLayoutEffect里加6秒定时器能稳定拿到正确值,但会导致样式延迟偏移,而且固定等待时间完全不可靠。现在需要一个不用定时器、能稳定获取准确高度的方案。

现有相关代码:

useLayoutEffect(() => {
    setTimeout(() => {
      let height = 0;
      setMinHeight(height);
      cardRefs.current.forEach(ref => {
        if (ref) {
          height = Math.max(height, ref.clientHeight);
        }
      });
      console.log(`ktest: maxHeight: ${height}`)
      setMinHeight(height);
    }, 6000)
  }, [comparedCardsData, numberOfColumns]);
import React, {useLayoutEffect, useRef, useState,} from 'react';
import {compose} from 'redux';
import {connect} from 'react-redux';
import SelectedCard from './SelectedCard';
import {createStructuredSelector} from "reselect";

const Header1 = ({
    data,
    numberOfColumns,
  }) => {
  const [minHeight, setMinHeight] = useState(0);
  const cardRefs = useRef([]);

  useLayoutEffect(() => {
    let height = 0;
    cardRefs.current.forEach(ref => {
      if (ref) {
        height = Math.max(height, ref.clientHeight);
      }
    });
    console.log(`height: ${height}`) // differs intermittently.
    setMinHeight(height);
  }, [data, numberOfColumns]);

  return data.slice(0, numberOfColumns)
    .map((card, index) => {
      return (
        <div>
          <div ref={ref => cardRefs.current[index] = ref} style={{minHeight}}>
            <SelectedCard/>
          </div>
          {/* many other components */}
        </div>
      );
    });
};

const mapStateToProps = createStructuredSelector({
  .....
});

const mapDispatchToProps = {
  .....
};

const all = compose(
  connect(mapStateToProps, mapDispatchToProps)
);

export default all(Header1);

解决方案

方案1:让SelectedCard主动通知父组件渲染完成

核心思路是让子组件在自身完全渲染(骨架屏消失、真实内容加载完毕)后,主动向父组件传递自身高度,父组件收集所有子组件的高度后计算最大值。

父组件修改

import React, {useRef, useState,} from 'react';
import {compose} from 'redux';
import {connect} from 'react-redux';
import SelectedCard from './SelectedCard';
import {createStructuredSelector} from "reselect";

const Header1 = ({
    data,
    numberOfColumns,
  }) => {
  const [minHeight, setMinHeight] = useState(0);
  // 存储已获取的卡片高度,key为索引,value为高度
  const cardHeights = useRef({});

  // 接收子组件传递的高度
  const handleCardReady = (index, height) => {
    cardHeights.current[index] = height;
    // 收集到所有卡片高度后计算最大值
    const heights = Object.values(cardHeights.current);
    if (heights.length === numberOfColumns) {
      const maxHeight = Math.max(...heights);
      setMinHeight(maxHeight);
    }
  };

  return data.slice(0, numberOfColumns)
    .map((card, index) => {
      return (
        <div key={index}>
          <div style={{minHeight}}>
            <SelectedCard onReady={(height) => handleCardReady(index, height)} />
          </div>
          {/* many other components */}
        </div>
      );
    });
};

// 以下原有代码不变
const mapStateToProps = createStructuredSelector({
  .....
});

const mapDispatchToProps = {
  .....
};

const all = compose(
  connect(mapStateToProps, mapDispatchToProps)
);

export default all(Header1);

子组件SelectedCard修改

在SelectedCard内部,当骨架屏消失、真实内容渲染完成后,通过props回调把自身高度传给父组件:

// SelectedCard.jsx示例
import React, {useRef, useEffect, useState} from 'react';

const SelectedCard = ({onReady}) => {
  const cardRef = useRef(null);
  const [isLoaded, setIsLoaded] = useState(false);

  // 替换为你真实的数据加载/骨架屏切换逻辑
  useEffect(() => {
    fetchYourData().then(() => {
      setIsLoaded(true);
    });
  }, []);

  // 加载完成后通知父组件高度
  useEffect(() => {
    if (isLoaded && cardRef.current && onReady) {
      const height = cardRef.current.clientHeight;
      onReady(height);
    }
  }, [isLoaded, onReady]);

  return (
    <div ref={cardRef}>
      {isLoaded ? (
        // 真实内容区
        <div>你的卡片内容</div>
      ) : (
        // 骨架屏区
        <div className="skeleton">骨架屏内容</div>
      )}
    </div>
  );
};

export default SelectedCard;

方案2:用ResizeObserver监听高度变化(无需修改子组件)

如果无法修改SelectedCard组件,可以用ResizeObserver监听父容器的高度变化,直到高度稳定(不再变化)时再设置minHeight。

import React, {useRef, useState, useEffect} from 'react';
import {compose} from 'redux';
import {connect} from 'react-redux';
import SelectedCard from './SelectedCard';
import {createStructuredSelector} from "reselect";

const Header1 = ({
    data,
    numberOfColumns,
  }) => {
  const [minHeight, setMinHeight] = useState(0);
  const cardRefs = useRef([]);
  // 存储每个卡片的最后一次高度,用于判断是否稳定
  const lastHeights = useRef({});
  const resizeObservers = useRef([]);

  useEffect(() => {
    // 清空之前的监听
    resizeObservers.current.forEach(observer => observer.disconnect());
    resizeObservers.current = [];
    lastHeights.current = {};

    data.slice(0, numberOfColumns).forEach((_, index) => {
      const ref = cardRefs.current[index];
      if (!ref) return;

      const observer = new ResizeObserver((entries) => {
        const currentHeight = entries[0].contentRect.height;
        // 高度两次一致则判定为稳定
        if (lastHeights.current[index] === currentHeight) {
          lastHeights.current[index] = currentHeight;
          // 检查所有卡片是否都稳定
          const allStable = Object.keys(lastHeights.current).length === numberOfColumns;
          if (allStable) {
            const maxHeight = Math.max(...Object.values(lastHeights.current));
            setMinHeight(maxHeight);
            // 稳定后断开监听,减少不必要触发
            observer.disconnect();
          }
        } else {
          lastHeights.current[index] = currentHeight;
        }
      });

      observer.observe(ref);
      resizeObservers.current.push(observer);
    });

    // 组件卸载时清理监听
    return () => {
      resizeObservers.current.forEach(observer => observer.disconnect());
    };
  }, [data, numberOfColumns]);

  return data.slice(0, numberOfColumns)
    .map((card, index) => {
      return (
        <div key={index}>
          <div ref={ref => cardRefs.current[index] = ref} style={{minHeight}}>
            <SelectedCard/>
          </div>
          {/* many other components */}
        </div>
      );
    });
};

// 以下原有代码不变
const mapStateToProps = createStructuredSelector({
  .....
});

const mapDispatchToProps = {
  .....
};

const all = compose(
  connect(mapStateToProps, mapDispatchToProps)
);

export default all(Header1);

方案3:依赖SelectedCard的加载状态同步计算

如果SelectedCard的加载状态可以通过props传递给父组件,父组件可以在所有卡片加载完成后再计算高度:

import React, {useRef, useState, useEffect} from 'react';
import {compose} from 'redux';
import {connect} from 'react-redux';
import SelectedCard from './SelectedCard';
import {createStructuredSelector} from "reselect";

const Header1 = ({
    data,
    numberOfColumns,
  }) => {
  const [minHeight, setMinHeight] = useState(0);
  const cardRefs = useRef([]);
  // 跟踪每个卡片的加载状态
  const [cardLoadedStatus, setCardLoadedStatus] = useState({});

  // 所有卡片加载完成后计算高度
  useEffect(() => {
    const allLoaded = Object.values(cardLoadedStatus).every(status => status);
    if (allLoaded) {
      let maxHeight = 0;
      cardRefs.current.forEach(ref => {
        if (ref) {
          maxHeight = Math.max(maxHeight, ref.clientHeight);
        }
      });
      setMinHeight(maxHeight);
    }
  }, [cardLoadedStatus]);

  return data.slice(0, numberOfColumns)
    .map((card, index) => {
      return (
        <div key={index}>
          <div ref={ref => cardRefs.current[index] = ref} style={{minHeight}}>
            <SelectedCard 
              onLoadComplete={() => {
                setCardLoadedStatus(prev => ({...prev, [index]: true}));
              }}
            />
          </div>
          {/* many other components */}
        </div>
      );
    });
};

// 以下原有代码不变
const mapStateToProps = createStructuredSelector({
  .....
});

const mapDispatchToProps = {
  .....
};

const all = compose(
  connect(mapStateToProps, mapDispatchToProps)
);

export default all(Header1);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 14:46:02