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

React组件中如何获取子元素加载完成后的高度?

React组件中如何获取子元素加载完成后的高度?

我明白你现在的困扰——当子组件(比如你写的文章列表PublicationsList)加载完数据、高度发生变化后,父组件ContentWrapper没法及时拿到更新后的真实高度,用setTimeout虽然能临时解决,但确实不够优雅,还容易因为时序问题出bug。下面给你几个更靠谱的解决方案:

方案1:用ResizeObserver监听容器高度变化

浏览器原生的ResizeObserver专门用来监听元素的尺寸变化,不管子组件怎么动态改变高度,只要容器的高度更新了,它就能立刻触发回调,拿到最新的高度值,这比setTimeout靠谱多了。

修改你的ContentWrapper组件里的useEffect:

import cls from './ContentWrapper.module.css';
import { classNames } from '../../lib/classNames/classNames.ts';
import { ReactNode, useContext, useEffect, useRef } from 'react';
import { ScrollContext } from '@/app/providers/ScrollProvider/ScrollProvider.tsx';
import { useLocation } from 'react-router';

interface ContentWrapperProps {
    className?: string;
    width?: '60' | '80' | '100';
    children?: ReactNode;
}
export const ContentWrapper = (props: ContentWrapperProps) => {
    const { className, children, width = '100' } = props;
    const { scrolls, setScroll } = useContext(ScrollContext);
    const { pathname } = useLocation();
    const onScroll = () => {
        const scroll = Math.round(window.scrollY);
        setScroll?.(pathname, scroll);
    };
    const ref = useRef<HTMLDivElement | null>(null);

    useEffect(() => {
        // 创建ResizeObserver实例监听容器尺寸变化
        const heightObserver = new ResizeObserver((entries) => {
            for (const entry of entries) {
                const latestHeight = entry.target.scrollHeight;
                console.log('最新的Wrapper高度:', latestHeight);
                // 这里可以执行你需要的逻辑,比如更新滚动状态
            }
        });

        const currentWrapper = ref.current;
        if (currentWrapper) {
            heightObserver.observe(currentWrapper);
        }

        // 组件卸载时停止监听,避免内存泄漏
        return () => {
            if (currentWrapper) {
                heightObserver.unobserve(currentWrapper);
            }
            window.removeEventListener('scroll', onScroll);
        };
    }, [pathname, setScroll]); // 加上必要依赖,避免不必要的重复监听

    return (
        <div
            className={classNames([
                className,
                cls.contentWrapper,
                [cls['size_' + width]],
            ])}
            id="contentBlockId"
            onScroll={onScroll}
            ref={ref}
        >
            {children}
        </div>
    );
};

方案2:让子组件主动通知父组件高度更新

如果子组件PublicationsList明确知道自己什么时候加载完数据、高度发生变化(比如数据请求完成后),可以通过props传递回调函数,主动通知父组件“我加载完了,高度变了”,这样父组件就能在精准的时机拿到最新高度。

第一步:修改PublicationsList组件,添加回调props

import cls from './PublicationsList.module.css';
import { classNames } from '@/shared/lib/classNames/classNames.ts';
import { useAllPublications } from '../../api/publicationApi.ts';
import { Publication } from '../../model/types/publication.ts';
import { Loader } from '@/shared/ui/Loader/Loader.tsx';
import { PublicationTapeItem } from '../PublicationTapeItem/PublicationTapeItem.tsx';
import { useEffect, useRef } from 'react';

// 新增onHeightChange回调props,用于通知父组件高度变化
interface PublicationsListProps {
    className?: string;
    onHeightChange?: () => void;
}
export const PublicationsList = (props: PublicationsListProps) => {
    const { className, onHeightChange } = props;
    const { data, isLoading, error } = useAllPublications();
    const ref = useRef<HTMLDivElement | null>(null);

    // 当数据加载完成/更新时,触发回调通知父组件
    useEffect(() => {
        if (data && ref.current) {
            onHeightChange?.();
        }
    }, [data, onHeightChange]); // 依赖data,数据更新就触发

    if (isLoading) {
        return <Loader />;
    }

    if (error) {
        throw new Error(error.toString());
    }

    return (
        <div
            className={classNames([className, cls.publicationsList], {})}
            ref={ref}
        >
            {data.map((publication: Publication) => (
                <PublicationTapeItem
                    key={publication.id}
                    publication={publication}
                />
            ))}
        </div>
    );
};

第二步:在父组件中使用这个回调

// 假设在某个页面中使用ContentWrapper和PublicationsList
import { useRef } from 'react';
import { ContentWrapper } from './ContentWrapper';
import { PublicationsList } from './PublicationsList';

const SomePage = () => {
    const wrapperRef = useRef<HTMLDivElement | null>(null);
    // ... 其他逻辑,比如scrolls、pathname等

    return (
        <ContentWrapper ref={wrapperRef}>
            <PublicationsList onHeightChange={() => {
                // 此时就能拿到ContentWrapper的最新高度
                console.log('子组件加载完成后的高度:', wrapperRef.current?.scrollHeight);
                // 也可以在这里执行滚动位置恢复等逻辑
                // const savedScroll = scrolls[pathname];
                // if (savedScroll) window.scrollTo(0, savedScroll);
            }} />
        </ContentWrapper>
    );
};

如果需要在ContentWrapper内部直接处理逻辑,也可以给ContentWrapper新增onContentHeightChange的props,把回调逻辑封装在组件内部。

为什么原来的setTimeout不是好选择?

你用setTimeout(20)能拿到高度,本质是赌20ms后子组件已经渲染完了,但这个时间是硬编码的:如果网络慢、数据加载久,20ms可能不够,还是会拿到旧高度;如果渲染快,又会多等不必要的时间,而且这种“等待”的方式很容易出现时序相关的bug,完全不如上面两种方案可靠。

备注:内容来源于stack exchange,提问作者Алексей Смирнов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 13:24:51