测试时React组件ref.current始终为undefined的问题求助
问题分析与解决方案
问题根源
你当前的代码存在两个核心问题:
- ref初始化时机错误:
isDisabled是在组件渲染阶段同步计算的,而React要到commit阶段才会给ref.current赋值DOM元素,所以初始渲染时containerRef.current必然是undefined,导致isDisabled始终为true,按钮被错误禁用。 - 子元素计数逻辑错误:容器内包含
<h3>标签,直接用children.length会把标题也算入数量,导致判断逻辑失效。
解决方案一:用useEffect延迟计算禁用状态
通过useState存储禁用状态,在useEffect中等待DOM挂载完成后,再准确计算按钮数量并更新状态:
import React, { useRef, useState, useEffect } from 'react'; const Component = () => { const containerRef = useRef(); const [isDisabled, setIsDisabled] = useState(true); useEffect(() => { if (!containerRef.current) return; // 只统计容器内的Button元素,排除标题 const buttonElements = Array.from(containerRef.current.children).filter( el => el.tagName === 'BUTTON' ); setIsDisabled(buttonElements.length < 2); }, [roster]); // 依赖roster,数据变化时重新计算 return ( <div ref={containerRef}> <h3>Title</h3> {roster.map((category) => category.positions.every((position) => position.isSelected) ? ( <Button disabled={isDisabled} {...otherProps} /> ) : ( category.positions.map( (position) => position.isSelected && ( <Button disabled={isDisabled} {...otherProps} /> ) ) ) )} </div> ); };
解决方案二:直接从数据源计算按钮数量(更推荐)
既然按钮渲染逻辑完全依赖roster数据,直接从数据推导渲染数量,避免依赖DOM,逻辑更可靠且测试更简单:
import React from 'react'; const Component = () => { // 直接从roster计算会渲染的按钮总数 const getButtonCount = () => { let count = 0; roster.forEach(category => { if (category.positions.every(p => p.isSelected)) { count += 1; } else { count += category.positions.filter(p => p.isSelected).length; } }); return count; }; const isDisabled = getButtonCount() < 2; return ( <div> <h3>Title</h3> {roster.map((category) => category.positions.every((position) => position.isSelected) ? ( <Button disabled={isDisabled} {...otherProps} /> ) : ( category.positions.map( (position) => position.isSelected && ( <Button disabled={isDisabled} {...otherProps} /> ) ) ) )} </div> ); };
测试代码调整
无论用哪个方案,都可以在测试中加入waitFor确保组件状态更新完成:
it('calls handleClick when a button is clicked', async () => { const { user } = render( <Component {...defaultProps} rosterPositionsConfig={config}/> ); // 等待按钮渲染并完成状态更新 await waitFor(() => screen.getAllByRole('button')); const firstButton = screen.getAllByRole('button')[0]; await user.click(firstButton); expect(defaultProps.handleClick).toHaveBeenCalledTimes(1); });
内容的提问来源于stack exchange,提问作者Avi
相关产品推荐
相关产品推荐

