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,提问作者Алексей Смирнов
相关产品推荐
相关产品推荐

