Tengo este ejemplo simple de un componente que usa useRef() para contar la cantidad de veces que mi componente se ha renderizado y useState() para mantener algún estado:
const {useState, useRef} = React; const App = () => { const [counter, setCounter] = useState(0); const renderCount = useRef(0); const onClick = () => { setCounter(1); } renderCount.current++; alert("rendering"); // executes 3 times, not twice. return <div> <p>Render count: {renderCount.current}</p> <button onClick={onClick}>{counter}</button> </div> } ReactDOM.render(<App />, document.body); <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script> Cuando ejecuto el fragmento anterior, la alert se activa y el contador de referencias se incrementa debido al primer procesamiento. Cuando hago clic en el botón por primera vez, se llama a setCounter(1) , lo que provoca que el componente se vuelva a procesar/reejecutar (como se esperaba), lo que provoca que la alerta aparezca por segunda vez y que aumente el contador de referencia. Cuando hago clic en el botón por segunda vez, setCounter(1) (que no actualiza el estado porque ya es 1 ). Como el estado no cambia, esperaría que no se necesite volver a procesar y, por lo tanto, mi componente de la App no se ejecutará nuevamente. Sin embargo, veo una tercera alerta, que indica que el componente se ejecutó nuevamente, pero el contador de referencias no aumenta ni la salida renderizada cambia después del segundo clic. Más clics del botón no muestran alertas como se esperaba. Mi pregunta es ¿por qué React vuelve a ejecutar el componente de mi aplicación cuando el estado no cambia (es decir, en el segundo clic del botón)?
Este es el comportamiento de renderizado de React para volver a renderizar y este es un caso especial. Si actualiza el estado al mismo valor que el estado actual, entonces React vuelve a renderizar el componente una vez más y se salva de las renderizaciones subsiguientes.
Sin embargo, este renderizado innecesario no es el caso del renderizado inicial. Quiero decir, si usa setCounter(0) en el método onClick en lugar de setCounter(1) , entonces el componente se representará solo una vez, incluso si el método onClick se llama posteriormente.
El flujo es como en el siguiente diagrama:
La razón es; React no puede adivinar que la salida del renderizado no cambiará, tiene que renderizar una vez más y comparar los resultados con el renderizado anterior. Pero no estoy seguro de cómo se maneja en el estado inicial (tal vez una bandera). No hay mucha información al respecto en los documentos.
Puede pensar que hay un problema de eficiencia, pero en realidad no lo hay. Hay dos fases en el ciclo de renderizado de React: la fase de renderizado y la fase de confirmación . DOM se actualiza después de la fase de confirmación y, en esta situación, el proceso no irá a la fase de confirmación y no conducirá a la actualización de DOM.
No está utilizando el gancho useEffect() de React, es por eso que su función, llamada en cualquier momento, llama a sus funciones de ámbito local. Piense en el componente de su función como si fuera una letra. Se lo pasas al cartero cerrado y firmado. Pero en su caso, lo pasó abierto (durante la transferencia fue leído y llamado) pero firmado (sabe dónde estará), por lo tanto, necesita usar métodos de enlace/ciclo de vida para controlar su componente React.
Es por eso que está experimentando las alertas y los incrementos.
Si ya ha trabajado con los componentes de la clase React, entonces debe saber cuál es su ciclo de vida. Si no, te animo a leer este artículo: React Lifecycles .
useEffect tiene un comportamiento de conexión entre componentDidMount , componentDidUpdate y componentWillUnmount . Cada vez que actualice su estado o accesorios, se llamará a este método y ejecutará el código dado.