我已经使用 Javascript 几年了,以我目前对事件循环的了解,我很难理解为什么 this testing recipe来自 React 文档的工作。有人能够准确地分解那里每一步发生的事情吗?对我来说,这在测试中起作用似乎很神奇:
await act(async () => {
render(<User id="123" />, container);
});
// expect something
该组件看起来像这样(复制以防该链接被弃用):function User(props) {
const [user, setUser] = useState(null);
async function fetchUserData(id) {
const response = await fetch("/" + id);
setUser(await response.json());
}
useEffect(() => {
fetchUserData(props.id);
}, [props.id]);
if (!user) {
return "loading...";
}
return (
<details>
<summary>{user.name}</summary>
<strong>{user.age}</strong> years old
<br />
lives in {user.address}
</details>
);
}
渲染上没有隐式或显式返回,那么 act 如何知道等待组件中发生的异步内容(获取等)?对我来说,这会更有意义:
await act(async () => render(<User id="123" />, container));
或(这是同一件事):await act(async () => {
return render(<User id="123" />, container);
});
甚至:await act(render(<User id="123" />, container));
但这似乎不是人们如何使用它,或者它是如何被使用的,所以我有点迷茫。我见过 enzyme 的相同例子mount
.我不想创建一个脆弱的测试,所以我真的很想了解这一点。
它是否与异步回调有关,即最后是否将某些内容附加到事件循环中,从而使等待等待渲染中的所有内容在解析之前发生?
我在这里画了一个空白,在 react doc 丛林中挣扎,因为 大家 似乎使用这种模式,但没有人真正解释它为什么或如何工作。
我在这里先向您的帮助表示感谢!
最佳答案
仔细查看 react-dom
的源代码时和 react-dom/test-utils
似乎是什么让整个事情成功的是this setImmediate call在 recursivelyFlushAsyncActWork 中的第一次效果刷新之后发生.
好像是 act
选择使用这个recursivelyFlushAsyncActWork
仅仅因为回调具有“thenable”的签名,即 Promise。你可以看到这个here .
这应该意味着发生的事情是(简化的):
useEffect
回调被刷新(将 fetch
放在事件循环中)。 setImmediate
回调“确保”我们的模拟 promise/fetch 得到解决。 setImmediate
内的递归发生。回调(由 enqueueTask
调用)使状态更改出现在 DOM 中。 resolve
和我们的act
解决。 在看起来有点像这样的代码中(除了这是从我的 React 项目的 node_modules 的旧版本
react-dom
中获取的,现在 flushWorkAndMicroTasks
似乎被称为 recursivelyFlushAsyncActWork
):function flushWorkAndMicroTasks(resolve) {
try {
flushWork(); // <- First effect flush (fetch will be invoked by the useEffect?)
enqueueTask(function () { // <- setImmediate is called in here (finishes the fetch)
if (flushWork()) { // <- Flush one more time and the next loop this will be false
flushWorkAndMicroTasks(resolve);
} else {
resolve(); // <- resolve is called when flushWork has nothing left to flush.
}
});
} catch (err) {
resolve(err);
}
}
附加信息(更新)除非我弄错了,否则这应该意味着
await act(async () => { render(...); });
"only"等待一个事件循环,除非最新刷新添加了新任务。 IE。如果两者之间存在刷新,则可以递归地添加 promise ,诸如 promise 链之类的微任务也可能会在第一个循环期间解决(因为它们在技术上是“阻塞”(source))。这意味着如果您在模拟或 React 代码中添加一个计时器或其他可能自然需要多个循环来解决的代码,则不会等待它,因为在解析之前没有捕获“after”事件,因为 then 监听器没有附加/回到了外在的 promise (如果我错了,请纠正我!)。
关于javascript - 为什么这个使用 await/act/async 的官方 React 测试配方真的有效?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69775101/