Dar los códigos a continuación
import { useState } from "react"; import "./styles.css"; export default function App() { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); const inc1 = () => { console.log("debug inc:", count1); setCount1((prev) => prev + 1); }; const inc2 = () => { console.log("debug inc2:", count2); setCount2(count2 + 1); }; const [processInc1] = useState(() => { console.log("debug longProcessBeforeInc:", count1); // Run some long process here return inc1; }); const [processInc2] = useState(() => { console.log("debug longProcessBeforeInc:", count2); // Run some long process here return inc2; }); console.log("debug render:", count1, count2); return ( <div className="App"> <h3> {count1} - {count2} </h3> <button onClick={inc1}>Inc 1</button> <br /> <br /> <button onClick={inc2}>Inc 2</button> <br /> <br /> <button onClick={processInc1}>Long Inc 1</button> <br /> <br /> <button onClick={processInc2}>Long Inc 2</button> </div> ); } inc1 , inc2 , processInc1 todo funciona como se esperaba cuando aumenta el valor en 1 y se procesa correctamente.
Así que con inc1 , inc2 la principal diferencia es setCount1((prev) => prev + 1); y setCount2(count2 + 1); , y processInc1 , processInc2 básicamente devuelven inc1 e inc2 respectivamente a través de useState la primera vez que se procesa el componente.
Entiendo desde aquí https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function que tiene algo que ver con el cierre pero Dado el ejemplo anterior, no entiendo por qué inc2 y processInc1 funcionan, pero no processInc2 .
Aquí está el enlace a codesandbox para lo anteriorhttps://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js
Tu estás en el camino correcto. Como decías, este problema en realidad es causado por el uso del cierre. Entonces, comencemos mostrándote una buena definición de cierre:
Un cierre es la combinación de una función y el entorno léxico dentro del cual se declaró esa función. Este entorno consta de cualquier variable local que estuviera dentro del alcance en el momento en que se creó el cierre.
Documentos: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Esto significa que cuando devuelve el cierre inc2 en useState, también está creando una copia de las variables que se usan en ese momento en particular (es decir, representación inicial) por ese entorno léxico (esto incluye el valor count2). Esta es la razón por la cual processInc2 seguirá teniendo el mismo valor anterior de count2.
Al mismo tiempo, la función processInc1 funcionará correctamente porque, al usar una devolución de llamada en useState, siempre obtendrá el valor actual del estado count1.
Por último, inc2 funciona porque lo está llamando directamente cuando hace clic en el botón, por lo que el valor de count2 se evalúa en el momento en que lo llama (por lo tanto, lo más probable es que tenga el valor actual).
Un detalle muy importante aquí es que inc1 e inc2 se redefinen cada vez que se procesa el componente de la App .
function App() { // ... const inc1 = () => { console.log("debug inc:", count1); setCount1((prev) => prev + 1); }; // ... }Luego almacena esas 2 funciones dentro de un estado, que nunca cambia.
const [processInc1] = useState(() => { console.log("debug longProcessBeforeInc:", count1); // Run some long process here return inc1; }); Esto dará como resultado processInc1 y processInc2 que apuntan a la primera definición de inc1 e inc2 (creada en el primer renderizado).
La razón por la que count1 y count2 nunca se actualizan en esta primera versión de la función es que las variables nunca se reasignan. Esto es por diseño.
La única razón por la que count1 y count2 cambian en una representación futura es porque useState() devolverá el nuevo valor. Después de recibir este nuevo valor, inc1 e inc2 se redefinen.
processInc1 y processInc2 luego se extraen de un estado React que contiene la primera definición de inc1 e inc2 , por lo que el uso de count1 y count2 dentro de esas funciones se referirá al primer valor de count1 y count2 .
Cuando setCount2(count2 + 1) dentro de inc2 y lo llama a través de processInc2 , el valor de count2 sigue siendo 0 y nunca cambiará. Esto se debe a que processInc2 hace referencia a la primera definición de inc2 y no a la definición actual.
setCount1((prev) => prev + 1) funciona debido a la diferente firma de la función. Donde inc2 pasa un valor estático ( 0 + 1 ) al setter, inc1 pasa una transformación (como devolución de llamada). Cuando pasa una función a setCount1 , React llamará a esa función con el estado actual como único argumento. A continuación, el valor de retorno se utiliza como el nuevo estado. Entonces, aunque processInc1 todavía usa la primera definición de inc1 . Siempre será relevante, ya que describe la transformación que debe realizarse en lugar del valor que debe establecerse.
Tenga en cuenta que console.log("debug inc:", count1); seguirá registrando 0 cuando se le llame a través de processInc1 por las razones anteriores.