React中useRef数组访问元素返回undefined问题排查
你的代码里有几个关键问题导致refs访问返回undefined,咱们一步步拆解清楚:
1. 把React元素存入state是反模式
你通过items.map生成的<div>元素被存入了renderedItems state,这不符合React的设计逻辑。React元素应该直接在组件的渲染阶段(也就是return语句里)生成,而不是存在state中。这种做法会触发不必要的组件重渲染,还会打乱ref绑定与组件更新的同步节奏。
2. 对createRef的使用存在误解
你用React.createRef()创建的是一个带有current属性的容器对象,不是直接指向DOM节点。你原来的代码里直接访问refsArray.current[0].offsetHeight,但refsArray.current[0]只是这个空容器,它本身没有offsetHeight属性,自然返回undefined——正确的访问方式应该是refsArray.current[0].current.offsetHeight,而且只有当DOM节点挂载完成后,current才会指向真实的div元素。
3. useEffect与ref绑定的时机不匹配
你的第一个useEffect依赖renderedItems,当调用setRenderedItems后这个effect会立刻执行,但此时React还没把新生成的<div>挂载到DOM上,createRef的current属性还没被赋值,所以即使你补了.current,拿到的依然是undefined。
4. ref数组的初始化方式不够可靠
在仅执行一次的useEffect里给refsArray赋值createRef,当组件后续因其他原因重渲染时,这个逻辑不会再执行,万一数组长度变化或者组件重新挂载,ref绑定会出现混乱。回调ref是处理多ref场景更可靠的方案。
修复后的完整代码
import React from "react"; const items = [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10] ]; export default function Component() { const [height, setHeight] = React.useState(0); const refsArray = React.useRef([]); // 用回调ref同步DOM节点与refs数组 const setRef = (element, index) => { refsArray.current[index] = element; }; React.useEffect(() => { // 先判断DOM节点是否存在,避免挂载前访问报错 const firstDiv = refsArray.current[0]; if (firstDiv) { const heightOfFirstDiv = firstDiv.offsetHeight; console.log("heightOfFirstDiv", heightOfFirstDiv); setHeight(heightOfFirstDiv); } }, []); // 如果items是动态变化的,记得把items加入依赖数组 return ( <div> The height is: {height || "undefined"} {/* 直接在渲染阶段生成列表元素,用回调ref绑定 */} {items.map((item, index) => ( <div key={index} // 列表渲染必须加key,这是React的强制要求 ref={(el) => setRef(el, index)} style={{ height: 100, width: 40 }} > {item} </div> ))} </div> ); }
关键修复点说明
- 移除了
renderedItemsstate:直接在return里通过items.map生成元素,回归React正常的渲染流程。 - 改用回调ref:回调函数会在DOM节点挂载/卸载时自动触发,确保refs数组始终与真实DOM节点同步,而且不需要再处理
createRef的current属性,直接拿到DOM元素。 - 添加了key属性:列表渲染必须加唯一key,避免不必要的重渲染和React警告。
- 增加存在性检查:在useEffect里先判断DOM节点是否存在,避免组件挂载前访问ref导致的报错。
如果你的items是动态更新的,记得把items添加到useEffect的依赖数组中,这样当列表内容变化时,能重新获取最新的DOM高度。
内容的提问来源于stack exchange,提问作者Mike K

