React中如何监听容器子元素数量变化并解决ref取值报错问题
问题根因
你遇到的报错和状态不同步问题来自三个核心错误:
- 首次渲染时
useRef创建的ref对象current属性默认值为undefined,你在useEffect依赖数组中直接访问container.current.children.length,此时ref还未绑定到DOM节点,直接读取属性就会抛出Cannot read properties of undefined (reading 'children')错误。 - React的
useEffect依赖对比机制只能响应React可控的state、props变化,DOM节点的children.length是原生DOM属性,它的变动不会触发React重渲染和依赖检查,哪怕写在依赖数组里也无法监听到子节点数量变动,必然出现状态和实际值不同步的问题。 - 你调用
setItems时存入的是子元素数量(数字类型),后续却读取items.length,数字本身没有length属性,逻辑本身存在错误,这也是items.length !== children.length问题的直接诱因。
实现方案
方案1:直接从React数据源获取长度(优先推荐)
你的子元素是通过children.map渲染生成的,子元素数量完全由传入的children属性决定,完全不需要操作DOM读取长度,从根源规避ref空值、监听失效问题:
const container = useRef(); // 初始值设为0,避免初始渲染时值为undefined const [itemsCount, setItemsCount] = useState(0); useEffect(() => { setItemsCount(children.length); }, [children]); return( <div ref={container}> {children.map(item => <Item key={item.id}/>)} </div> )
这种写法完全遵循React数据驱动的设计逻辑,没有额外DOM操作,状态同步最稳定,性能最好。
方案2:需要监听原生DOM变动时使用MutationObserver
如果容器内的子元素可能被第三方脚本、原生DOM API直接修改(不受React渲染逻辑控制),使用浏览器原生的MutationObserver监听DOM子节点变化,不要依赖useEffect监听DOM属性:
const container = useRef(); const [itemsCount, setItemsCount] = useState(0); useEffect(() => { const targetDom = container.current; if (!targetDom) return; // 初始化时先同步一次长度 setItemsCount(targetDom.children.length); // 创建DOM变动监听器 const observer = new MutationObserver(() => { setItemsCount(targetDom.children.length); }); // 配置监听规则:仅监听直接子节点的增删 observer.observe(targetDom, { childList: true, subtree: false // 不需要监听深层节点变动时保持关闭,减少性能消耗 }); // 组件卸载时断开监听,避免内存泄漏 return () => observer.disconnect(); }, []); // 空依赖即可,仅在组件挂载时绑定一次监听 return( <div ref={container}> {children.map(item => <Item key={item.id}/>)} </div> )
注意:不要把
container.current或其下属DOM属性写入useEffect依赖数组,ref的current属性变动不会触发组件重渲染,写在依赖中没有实际作用,反而容易引发空值报错。
内容的提问来源于stack exchange,提问作者Alyona
相关产品推荐
相关产品推荐

