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
相关产品推荐
相关产品推荐

