"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Reaccionar: cierre obsoleto

Reaccionar: cierre obsoleto

Publicado el 2024-09-02
Navegar:951

En esta publicación, mostraré cómo crear un cierre en una aplicación UseState Hook React.

No explicaré qué es un cierre, porque hay muchos recursos sobre este tema y no quiero ser repetitivo. Recomiendo la lectura de este artículo de @imranabdulmalik.

En resumen, un cierre es (de Mozilla):

... la combinación de una función agrupada (encerrada) con referencias a su estado circundante (el entorno léxico). En otras palabras, un cierre le da acceso al alcance de una función externa desde una función interna. En JavaScript, los cierres se crean cada vez que se crea una función, en el momento de la creación de la función.

En caso de que no estés familiarizado con el término entorno léxico, puedes leer este artículo de @soumyadey o, alternativamente, este.

el problema

En una aplicación React, puedes crear accidentalmente un cierre de una variable que pertenece al estado del componente creado con el gancho useState. Cuando esto sucede, te enfrentas a un problema de cierre obsoleto, es decir, cuando haces referencia a un valor antiguo del estado que mientras tanto ha cambiado, y por lo tanto no es más relevante.

POS

He creado una aplicación Demo React cuyo objetivo principal es incrementar un contador (perteneciente al estado) que se puede cerrar en un cierre en la devolución de llamada del método setTimeout.

En resumen, esta aplicación puede:

  • Mostrar el valor del contador
  • Incrementar en 1 el contador
  • Inicia un cronómetro para incrementar el contador en 1 después de cinco segundos.
  • Incrementar en 10 el contador

En la siguiente imagen, se muestra el estado inicial de la interfaz de usuario de la aplicación, con el contador a cero.

React: stale closure

Simularemos el cierre del mostrador en tres pasos:

  1. Incrementando en 1 el contador

React: stale closure

  1. Iniciando el cronómetro para incrementar en 1 después de cinco segundos

React: stale closure

  • Incrementar en 10 antes de que se active el tiempo de espera React: stale closure

Después de 5 segundos, el valor del contador es 2.

React: stale closure

El valor esperado del contador debería ser 12, pero obtenemos 2.

La razón por la que esto sucede es porque hemos creado un cierre del contador en la devolución de llamada pasada a setTimeout y cuando se activa el tiempo de espera configuramos el contador a partir de su valor anterior (que era 1).

setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter   1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);

Siguiendo el código completo del componente de la aplicación.

function App() {
  const [counter, setCounter] = useState(0)
  const timeOutInSeconds: number = 5
  const [startTimeout, setStartTimeout] = useState(false)
  const [timeoutInProgress, setTimeoutInProgress] = useState(false)
  const [logs, setLogs] = useState>([])

  useEffect(() => {
    if (startTimeout && !timeoutInProgress) {
      setTimeoutInProgress(true)
      setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`])
      setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter   1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);
    }
  }, [counter, startTimeout, timeoutInProgress])

  function renderLogs(): React.ReactNode {
    const listItems = logs.map((log, index) =>
      
  • {log}
  • ); return
      {listItems}
    ; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    Solución

    La solución se basa en el uso del gancho useRef que le permite hacer referencia a un valor que no es necesario para la representación.

    Entonces agregamos al componente de la aplicación:

    const currentCounter = useRef(counter)
    

    Luego modificaremos la devolución de llamada de setTimeout como se muestra a continuación:

    setTimeout(() => {
            setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`])
            setTimeoutInProgress(false)
            setStartTimeout(false)
            setCounter(currentCounter.current   1)
            setLogs((l) => [...l, `Did you create a closure of counter?`])
    
          }, timeOutInSeconds * 1000);
    

    Nuestra devolución de llamada necesita leer el valor del contador porque registramos el valor actual antes de incrementarlo.

    En caso de que no necesites leer el valor, puedes evitar el cierre del contador simplemente usando la notación funcional para actualizar el contador.

    seCounter(c => c   1)
    

    Recursos

    • Dmitri Pavlutin Tenga cuidado con los cierres obsoletos al utilizar ganchos de React
    • Imran Abdulmalik Dominando los cierres en JavaScript: una guía completa
    • Alcance léxico de Keyur Paralkar en JavaScript – Guía para principiantes
    • Souvik Paul Cierres obsoletos en React
    • Soumya Dey: comprensión del alcance léxico y cierres en JavaScript
    • Desbordamiento de pila de Subash Mahapatra
    Declaración de liberación Este artículo se reproduce en: https://dev.to/animusna/react-stale-closure-81a?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
    Último tutorial Más>

    Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

    Copyright© 2022 湘ICP备2022001581号-3