React中如何在Suspense异步内容加载完成后获取目标DOM的实际高度
React超长虚拟列表容器高度获取异常解法
原有方案失效的核心原因
- 之前不管是用
useEffect读高度,还是尝试官方推荐的回调ref,本质上都只会在容器刚挂载到页面时触发一次,触发的时间点早于Suspense异步内容渲染完成的时间点 - 承载列表内容的
.list容器从组件第一次渲染开始就一直在页面上,中途不会被卸载移除,哪怕容器内部内容从loading占位切换为真正的列表,容器本身的DOM引用根本没有变更,回调ref自然不会重新执行,也就读不到内容撑起来之后的真实高度 - 之前的思路默认容器高度是挂载后就固定不变的静态值,但这个场景下的容器高度本来就是动态变化的:可能初始loading阶段只有几百像素高,等列表数据、图片全部加载完成后会达到几千像素的总高度,用一次性读取的逻辑本来就适配不了这种动态变化的场景
低改动量的落地实现
完全不用把高度计算的逻辑塞进通用的SubWindow组件,只要把原来一次性读高度的逻辑,换成持续监听尺寸变化的逻辑即可,改动量非常小,还能完美适配异步加载场景:
- 优先选用现成的开箱即用的尺寸监听hook,这类hook底层已经帮你封装好了ResizeObserver的监听逻辑,不需要自己处理各种边界情况
- 改代码的时候根本不用动原来的组件结构:把之前自己写的ref、高度state、
useEffect那套逻辑,换成尺寸hook返回的引用和高度值即可,透传给SubWindow的ref链路和之前完全一样,根本不会破坏SubWindow作为通用组件的复用性
改造后的核心代码大概长这样:const BananaWindow = (props) => { // 替换原来的useRef + useState + useEffect逻辑 const [contentRef, { height: contentRefHeight }] = useMeasure(); // 原有的startIdx/endIdx计算逻辑完全不需要改动 const startIdx = Math.max(0, Math.floor(/* 原有基于滚动位置的计算逻辑 */) - 10) const endIdx = Math.min(/* 原有列表总条目数 */, Math.ceil(/* 原有基于滚动位置的计算逻辑 */) + 10) return ( <SubWindow ref={contentRef} title="all bananas" content={ <AllBananas data={props.data} startIdx={startIdx} endIdx={endIdx} /> } /> ) } - 要是不想引入第三方依赖,自己写个极简的尺寸监听hook也没几行代码,根本不复杂:
const useDomSize = () => { const domRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); useEffect(() => { if (!domRef.current) return; // 初始化时先同步一次初始尺寸 setSize(domRef.current.getBoundingClientRect()); const observer = new ResizeObserver(entries => { const { contentRect } = entries[0]; setSize({ width: contentRect.width, height: contentRect.height }); }); observer.observe(domRef.current); return () => observer.disconnect(); }, []); return [domRef, size]; };
这套方案之所以能适配Suspense的异步场景,核心是因为ResizeObserver根本不关心容器里面装的是什么、什么时候渲染完——只要容器的实际渲染尺寸发生变化,不管是从loading切到真实列表、还是图片加载完把容器撑高了、还是窗口大小变化导致容器跟着调整,都会自动拿到最新的尺寸值,完全不用手动去判断Suspense什么时候加载完成,从根源上解决了拿不到异步内容真实高度的问题。
内容的提问来源于stack exchange,提问作者Samson Liu
相关产品推荐
相关产品推荐

