You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试时React组件ref.current始终为undefined的问题求助

问题分析与解决方案

问题根源

你当前的代码存在两个核心问题:

  1. ref初始化时机错误:isDisabled是在组件渲染阶段同步计算的,而React要到commit阶段才会给ref.current赋值DOM元素,所以初始渲染时containerRef.current必然是undefined,导致isDisabled始终为true,按钮被错误禁用。
  2. 子元素计数逻辑错误:容器内包含<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 20:05:34