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.
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.
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:
En la siguiente imagen, se muestra el estado inicial de la interfaz de usuario de la aplicación, con el contador a cero.
Simularemos el cierre del mostrador en tres pasos:
Después de 5 segundos, el valor del contador es 2.
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 (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
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)
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