javascript - 为什么这个使用 await/act/async 的官方 React 测试配方真的有效?

标签 javascript reactjs asynchronous async-await

我已经使用 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 callrecursivelyFlushAsyncActWork 中的第一次效果刷新之后发生.
好像是 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/

    相关文章:

    javascript - 为什么 Array(x).join 会填充 x-1 个结果?在Javascript中

    javascript - 滚动表格以将其应用于 tbody

    node.js - 如何在node.js中设置超时?

    javascript - Bootstrap 导航栏折叠问题

    javascript - Lodash 更新数组之间的值

    reactjs - 如何修复React和Spring Web应用程序中的cors错误?

    node.js - npm 开始显示奇怪的错误 1

    javascript - Uncaught ReferenceError : Cannot access 'CONFIG' before initialization

    ASP.NET HttpContext.Current 在 Task.Run 中

    c# - 如何将 Table.ExecuteQuerySegmentedAsync() 与 Azure 表存储结合使用