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

React测试中act函数使用困惑及组件代码优化咨询

React测试中act函数使用困惑及组件代码优化咨询

嘿,我来帮你理清这个act的困惑,顺便给你的组件代码提些实用的优化建议~

一、先解决act警告和ESLint的矛盾问题

你遇到的这个两难情况,核心原因其实很简单:你没有await userEvent的异步操作。

现在Testing Library的userEvent(v14及以上版本)所有交互方法都是异步的,如果你不等待这些操作完成就去执行断言,React的状态更新可能还在后台进行,这就触发了"未包裹在act中"的警告。而ESLint不让你手动包act,是因为userEvent本身已经内置了act的处理逻辑,手动属于重复操作,反而不符合最佳实践。

解决方法超级简单,给你的userEvent调用加上await就行:

// 测试代码修改后
describe("App", () => {
  it("should show the pages listing", async () => {
    render(<App />);

    const [firstPageInput, lastPageInput] = await screen.findAllByRole("textbox");
    
    // 加上await等待交互完成
    await userEvent.type(firstPageInput, "1");
    await userEvent.type(lastPageInput, "10");
    await userEvent.click(screen.getByText(/list pages/i));

    expect(await screen.findByText("Odd")).toBeInTheDocument();
    expect(await screen.findByText("1, 3, 5, 7, 9")).toBeInTheDocument();
    expect(await screen.findByText("Even")).toBeInTheDocument();
    expect(await screen.findByText("2, 4, 6, 8, 10")).toBeInTheDocument();
  });
});

这样既消除了act警告,又符合ESLint的规则,完美解决你的问题~

二、React组件代码的优化建议

你的组件功能没问题,但可以调整得更符合React的惯用风格,同时提升可维护性和用户体验:

1. 优化事件处理与状态更新

你当前的handlePageChange是返回函数的写法,可以保留,但建议用函数式状态更新,避免闭包导致的旧状态问题:

const handlePageChange = (page: keyof Inputs) => (event: React.ChangeEvent<HTMLInputElement>) => {
  // 使用prevInputs确保拿到最新的状态
  setInputs(prevInputs => ({ ...prevInputs, [page]: event.target.value }));
};

2. 提取内部组件,让职责更单一

把Result组件从App内部提取出来,作为独立组件,这样它可以单独复用、测试,也让App组件的代码更简洁:

// 单独的Result组件
const Result = ({ submitted, inputs }: { submitted: boolean; inputs: Inputs }) => {
  if (!submitted) {
    return null;
  }

  // 增加输入验证,避免无效数字导致的错误
  const firstPageNum = Number(inputs.firstPage);
  const lastPageNum = Number(inputs.lastPage);
  
  if (isNaN(firstPageNum) || isNaN(lastPageNum) || firstPageNum > lastPageNum) {
    return <p>请输入有效的页码范围哦,起始页码不能大于结束页码~</p>;
  }

  const [firstList, secondList] = listPages(firstPageNum, lastPageNum);
  return (
    <>
      <PagesListing pages={firstList} />
      <PagesListing pages={secondList} />
    </>
  );
};

3. 使用form元素优化表单体验

用<form>包裹输入框和按钮,绑定onSubmit事件,这样用户按回车也能提交表单,符合原生表单的使用习惯:

const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault(); // 阻止默认的页面跳转
  setSubmitted(true);
};

// 渲染部分改为form包裹
<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="firstPage">First page</label>
    <input
      id="firstPage"
      type="number" // 改为number类型,限制用户只能输入数字
      value={inputs.firstPage}
      onChange={handlePageChange("firstPage")}
      required // 原生必填提示
    />
  </div>
  <div>
    <label htmlFor="lastPage">Last page</label>
    <input
      id="lastPage"
      type="number"
      value={inputs.lastPage}
      onChange={handlePageChange("lastPage")}
      required
    />
  </div>
  <button type="submit">List pages</button>
</form>

优化后的完整组件代码

import React, { useState } from "react";
import "./App.css";
import { PagesListing } from "./components/PagesListing";
import { listPages } from "./utils/listPages";

type Inputs = {
  firstPage: string;
  lastPage: string;
};

const Result = ({ submitted, inputs }: { submitted: boolean; inputs: Inputs }) => {
  if (!submitted) {
    return null;
  }

  const firstPageNum = Number(inputs.firstPage);
  const lastPageNum = Number(inputs.lastPage);
  
  if (isNaN(firstPageNum) || isNaN(lastPageNum) || firstPageNum > lastPageNum) {
    return <p>请输入有效的页码范围,起始页码必须小于等于结束页码</p>;
  }

  const [firstList, secondList] = listPages(firstPageNum, lastPageNum);
  return (
    <>
      <PagesListing pages={firstList} />
      <PagesListing pages={secondList} />
    </>
  );
};

function App() {
  const [inputs, setInputs] = useState<Inputs>({ firstPage: "", lastPage: "" });
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    setSubmitted(true);
  };

  const handlePageChange = (page: keyof Inputs) => (event: React.ChangeEvent<HTMLInputElement>) => {
    setInputs(prevInputs => ({ ...prevInputs, [page]: event.target.value }));
  };

  return (
    <div>
      <h1>App</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="firstPage">First page</label>
          <input
            id="firstPage"
            type="number"
            value={inputs.firstPage}
            onChange={handlePageChange("firstPage")}
            required
          />
        </div>
        <div>
          <label htmlFor="lastPage">Last page</label>
          <input
            id="lastPage"
            type="number"
            value={inputs.lastPage}
            onChange={handlePageChange("lastPage")}
            required
          />
        </div>
        <button type="submit">List pages</button>
      </form>
      <div>
        <Result submitted={submitted} inputs={inputs} />
      </div>
    </div>
  );
}

export default App;

这些优化让你的组件更符合React的设计理念,同时提升了用户体验和代码的可维护性~

备注:内容来源于stack exchange,提问作者Allan Juan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 14:27:58