React中如何获取动态子元素高度并解决ref获取时的无限循环问题
问题根因
- 函数组件无法直接接收ref属性:直接给
TimelineElement传递ref属性会被React过滤,不会进入props,所以子组件无法拿到ref绑定到DOM,导致你拿不到元素引用。 - 无限循环触发原因:
- 你在组件顶层每次渲染都会重新执行
data.map生成新的refs数组和components数组,两个数组的引用每次渲染都不一样 - 你把这两个变量放到了
useLayoutEffect的依赖数组里,每次渲染都会触发effect执行,effect里的setHeights又会触发组件重新渲染,形成死循环。
- 你在组件顶层每次渲染都会重新执行
修复后完整代码
import React, { forwardRef, useRef, useLayoutEffect, useState } from 'react'; // 用forwardRef包裹子组件,第二个参数接收外部传入的ref const TimelineElement = forwardRef<HTMLElement, {data: TimelineData}>(({data}, ref) => { return <div className="timeline-element" ref={ref}> <div className="timeline-element-title">{data.title}</div> <div className="timeline-element-details">{data.details}</div> </div>; }); TimelineElement.displayName = 'TimelineElement'; export const Timeline : React.FC<{data: TimelineData[]}> = ({data}) => { // 用useRef存储ref数组,引用稳定不会每次渲染变化 const refs = useRef<(HTMLElement | null)[]>(new Array(data.length).fill(null)); const [heights, setHeights] = useState<number[]>([]); useLayoutEffect(() => { // 计算所有元素的高度 const newHeights = refs.current.map(el => el?.offsetHeight || 0); setHeights(newHeights); // 仅当data变化时才需要重新计算高度 }, [data]); const components = data.map( (r, i) => <TimelineElement ref={(el) => refs.current[i] = el} data={r} key={r.id /* 建议给每个元素加唯一key,避免react渲染异常 */} /> ); return <div className="timeline"> {components} {JSON.stringify(heights)} </div>; }
补充说明
- 如果你的
data数组长度会动态变化,可以在useLayoutEffect里先同步更新refs.current的长度,避免下标不匹配的问题。 - 给列表元素加唯一
key是React列表渲染的最佳实践,可以避免很多异常渲染问题,建议用TimelineData里的唯一字段作为key,不要用下标。
内容的提问来源于stack exchange,提问作者Chrisstar
相关产品推荐
相关产品推荐

