He estado trabajando con Javascript durante un par de años y, con mi conocimiento actual del bucle de eventos, me cuesta entender por qué funciona esta receta de prueba de los documentos de React. ¿Alguien podría desglosar exactamente lo que sucede en cada paso allí? A mi me parece mágico que esto funcione en la prueba:
await act(async () => { render(<User id="123" />, container); }); // expect somethingEl componente se ve así (copiando en caso de que el enlace quede obsoleto):
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> ); }No hay un retorno implícito o explícito en el renderizado, entonces, ¿cómo sabe Act para esperar las cosas asíncronas que suceden en el componente (obtención, etc.)?
Para mí, esto tendría más sentido:
await act(async () => render(<User id="123" />, container));o (que es lo mismo):
await act(async () => { return render(<User id="123" />, container); });o incluso:
await act(render(<User id="123" />, container)); Pero no parece ser así como la gente lo usa, o cómo se pretendía que se usara, así que estoy un poco perdido. He visto los mismos ejemplos con mount de enzimas.
No quiero crear una prueba frágil, así que realmente quiero entender esto.
¿Tiene algo que ver con que la devolución de llamada sea asíncrona, es decir, agrega algo al ciclo de eventos al final, de modo que la espera espera a que suceda todo dentro del procesamiento antes de resolverse?
Me estoy quedando en blanco aquí y estoy luchando en la jungla de reacción doc, porque todo el mundo parece usar este patrón, pero nadie explica realmente por qué o cómo funciona.
¡Gracias por la ayuda por adelantado!
Al mirar más de cerca el código fuente de react-dom y react-dom/test-utils , parece que lo que hace que todo esto funcione es esta llamada setImmediate que ocurre después del primer efecto de descarga en recursivelyFlushAsyncActWork .
Parece que act elige usar este FlushAsyncActWork de forma recursivelyFlushAsyncActWork simplemente porque la devolución de llamada tiene la firma de ser "entonces posible", es decir, una Promesa. Puedes ver esto aquí .
Esto debería significar que lo que sucede es (simplificado) esto:
useEffect se vacía (poniendo fetch en el bucle de eventos).setImmediate "garantiza" que se resuelva nuestra promesa / recuperación simulada.setImmediate (llamada por enqueueTask ) que hace que los cambios de estado aparezcan en el DOM.resolve más externa y nuestro act se resuelve. En el código que se parece a esto (excepto que se tomó de una versión anterior de react-dom de node_modules de mi proyecto React, hoy en día flushWorkAndMicroTasks parece llamarse 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); } } A menos que me equivoque, esto debería significar que await act(async () => { render(...); }); "solo" espera un ciclo de eventos a menos que se agregue una nueva tarea con el último vaciado. Es decir, es posible agregar promesas de forma recursiva si hay un vaciado en el medio, también las micro tareas, como las cadenas de promesas, probablemente se resolverán durante el primer ciclo (ya que técnicamente están "bloqueando" ( fuente )).
Eso significa que si agrega un temporizador o algo más en su código simulado o React que, naturalmente, podría necesitar múltiples bucles para resolverse, no se esperará porque el evento "después" no se captura antes de resolverse, ya que los oyentes no están conectados / volvió a la promesa externa (¡corríjame si me equivoco!).