Saltar al contenido

¿Qué es el orden de ejecución de useEffect y su lógica de limpieza interna en los ganchos de reacción?

Nuestro grupo de trabajo ha pasado mucho tiempo investigando la resolución a tu búsqueda, te compartimos la respuestas y esperamos serte de gran ayuda.

Solución:

Una cosa que no está clara en las respuestas anteriores es el orden en el que se ejecutan los efectos cuando tiene varios componentes en la mezcla. Hemos estado haciendo un trabajo que implica la coordinación entre un padre y sus hijos a través de useContext, por lo que el orden nos importa más. useLayoutEffect y useEffect trabajar de diferentes formas en este sentido.

useEffect ejecuta la limpieza y el nuevo efecto antes de pasar al siguiente componente (profundidad primero) y hacer lo mismo.

useLayoutEffect ejecuta las limpiezas de cada componente (profundidad primero), luego ejecuta los nuevos efectos de todos los componentes (profundidad primero).

render parent
render a
render b
layout cleanup a
layout cleanup b
layout cleanup parent
layout effect a
layout effect b
layout effect parent
effect cleanup a
effect a
effect cleanup b
effect b
effect cleanup parent
effect parent
const Test = (props) => 
  const [s, setS] = useState(1)

  console.log(`render $props.name`)

  useEffect(() => 
    const name = props.name
    console.log(`effect $props.name`)
    return () => console.log(`effect cleanup $name`)
  )

  useLayoutEffect(() => 
    const name = props.name
    console.log(`layout effect $props.name`)
    return () => console.log(`layout cleanup $name`)
  )

  return (
    <>
      
      
      
    
  )


const Child = (props) => 
  console.log(`render $props.name`)

  useEffect(() => 
    const name = props.name
    console.log(`effect $props.name`)
    return () => console.log(`effect cleanup $name`)
  )

  useLayoutEffect(() => 
    const name = props.name
    console.log(`layout effect $props.name`)
    return () => console.log(`layout cleanup $name`)
  )

  return <>

Coloque estas tres líneas de código en un componente y verá su orden de prioridad.

  useEffect(() => 
    console.log('useEffect')
    return () => 
      console.log('useEffect cleanup')
    
  )

  window.requestAnimationFrame(() => console.log('requestAnimationFrame'))

  useLayoutEffect(() => 
    console.log('useLayoutEffect')
    return () => 
      console.log('useLayoutEffect cleanup')
    
  )

useLayoutEffect > requestAnimationFrame > useEffect

El problema que está experimentando es causado por loopRaf solicitando otro cuadro de animación antes de la función de limpieza para useEffect es ejecutado.

Más pruebas han demostrado que useLayoutEffect siempre se llama antes requestAnimationFrame y que su función de limpieza se llame antes de la siguiente ejecución evitando superposiciones.

Cambio useEffect para useLayoutEffect y debería resolver tu problema.

useEffect y useLayoutEffect se llaman en el orden en que aparecen en su código para tipos similares al igual que useState llamadas.

Puede ver esto ejecutando las siguientes líneas:

  useEffect(() => 
    console.log('useEffect-1')
  )
  useEffect(() => 
    console.log('useEffect-2')
  )
  useLayoutEffect(() => 
    console.log('useLayoutEffect-1')
  )
  useLayoutEffect(() => 
    console.log('useLayoutEffect-2')
  )

Hay dos ganchos diferentes en los que debería fijar sus ojos cuando trabaje con ganchos e intente implementar funcionalidades del ciclo de vida.

Según los documentos:

useEffect se ejecuta después de que reaccione su componente y se asegure de que su devolución de llamada de efecto no bloquee la pintura del navegador. Esto difiere del comportamiento en los componentes de la clase donde componentDidMount y
componentDidUpdate ejecutar sincrónicamente después de la renderización.

y por lo tanto usando requestAnimationFrame en estos ciclos de vida funciona sin parecer, pero tiene un pequeño problema con useEffect. Y, por lo tanto, useEffect debe usarse cuando los cambios que debe realizar no bloquean las actualizaciones visuales, como hacer llamadas a la API que conducen a un cambio en DOM después de recibir una respuesta.

Otro gancho que es menos popular pero es extremadamente útil cuando se trata de actualizaciones DOM visuales es useLayoutEffect. Según los documentos

La firma es idéntica a useEffect, pero se dispara sincrónicamente después de todas las mutaciones DOM. Use esto para leer el diseño del DOM y volver a renderizar sincrónicamente. Actualizaciones programadas en el interior useLayoutEffect se descargará sincrónicamente, antes de que el navegador tenga la oportunidad de pintar.

Entonces, si su efecto está mutando el DOM (a través de una referencia de nodo DOM) y la mutación DOM cambiará la apariencia del nodo DOM entre el momento en que se procesa y su efecto lo muta, entonces no quieres usar useEffect. Querrás usar useLayoutEffect. De lo contrario, el usuario podría ver un parpadeo cuando sus mutaciones DOM surtan efecto, que es exactamente el caso con requestAnimationFrame

//import React,  useState, useEffect  from "react";

const useState, useLayoutEffect = React;

//import ReactDOM from "react-dom";

function App() 
  const [startSeconds, setStartSeconds] = useState("");
  const [progress, setProgress] = useState(0);

  useLayoutEffect(() => 
    setStartSeconds(Math.random());

    const interval = setInterval(() => 
      setStartSeconds(Math.random());
    , 1000);

    return () => clearInterval(interval);
  , []);

  useLayoutEffect(
    () => 
      let raf = null;

      const onFrame = () => 
        const currentProgress = startSeconds / 120.0;
        setProgress(Math.random());
        // console.log(currentProgress);
        loopRaf();
        if (currentProgress > 100) 
          stopRaf();
        
      ;

      const loopRaf = () => 
        raf = window.requestAnimationFrame(onFrame);
        // console.log('Assigned Raf ID: ', raf);
      ;

      const stopRaf = () => 
        console.log("stopped", raf);
        window.cancelAnimationFrame(raf);
      ;

      loopRaf();

      return () => 
        console.log("Cleaned Raf ID: ", raf);
        // console.log('init', raf);
        // setTimeout(() => console.log("500ms later", raf), 500);
        // setTimeout(()=> console.log('5s later', raf), 5000);
        stopRaf();
      ;
    ,
    [startSeconds]
  );

  let t = [];
  for (let i = 0; i < 1000; i++) 
    t.push(i);
  

  return (
    

Hello CodeSandbox

progress t.map(e => ( progress ))
); ReactDOM.render(, document.querySelector("#root"));


Aquí puedes ver las comentarios y valoraciones de los lectores

Tienes la opción de añadir valor a nuestro contenido participando con tu experiencia en las notas.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4.5)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *