¿Funciona la actualización del estado por lotes de React cuando se utilizan ganchos?
Para componentes de clase, this.setState
llama por lotes si está dentro de los controladores de eventos. Pero, ¿qué sucede si el estado se actualiza fuera del controlador de eventos y se utiliza useState
un gancho?
function Component() {
const [a, setA] = useState('a');
const [b, setB] = useState('b');
function handleClick() {
Promise.resolve().then(() => {
setA('aa');
setB('bb');
});
}
return <button onClick={handleClick}>{a}-{b}</button>
}
¿ Se renderizará aa - bb
de inmediato? ¿O será aa - b
y entonces aa - bb
?
TL;DR : si los cambios de estado se activan de forma asincrónica (por ejemplo, envueltos en una promesa), no se agruparán; si se activan directamente, se agruparán.
Configuré una zona de pruebas para probar esto: https://codesandbox.io/s/402pn5l989
import React, { Fragment, useState } from 'react';
import ReactDOM from 'react-dom';
import './styles.css';
function Component() {
const [a, setA] = useState('a');
const [b, setB] = useState('b');
console.log('a', a);
console.log('b', b);
function handleClickWithPromise() {
Promise.resolve().then(() => {
setA('aa');
setB('bb');
});
}
function handleClickWithoutPromise() {
setA('aa');
setB('bb');
}
return (
<Fragment>
<button onClick={handleClickWithPromise}>
{a}-{b} with promise
</button>
<button onClick={handleClickWithoutPromise}>
{a}-{b} without promise
</button>
</Fragment>
);
}
function App() {
return <Component />;
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
Creé dos botones, uno activa los cambios de estado envueltos en una promesa como en su ejemplo de código, el otro activa los cambios de estado directamente.
Si miras la consola, cuando presiones el botón “con promesa”, primero mostrará a aa
y b b
, luego a aa
y b bb
.
Entonces la respuesta es no, en este caso, no se renderizará aa - bb
de inmediato, cada cambio de estado desencadena una nueva renderización, no hay procesamiento por lotes.
Sin embargo, cuando haces clic en el botón "sin promesa", la consola se mostrará a aa
e b bb
inmediatamente.
Entonces, en este caso, React procesa por lotes los cambios de estado y procesa ambos juntos.
Actualmente, en React v16 y versiones anteriores, solo las actualizaciones dentro de los controladores de eventos de React, como click
etc. onChange
, se agrupan de forma predeterminada. Entonces, al igual que las actualizaciones de estado de las clases, se agrupan de manera similar en los ganchos.
Existe una API inestable para forzar el procesamiento por lotes fuera de los controladores de eventos en casos excepcionales cuando lo necesite.
ReactDOM.unstable_batchedUpdates(() => { ... })
Existe un plan para agrupar todas las actualizaciones de estado en versiones futuras en React, probablemente v17 o superior.
Ahora también, si las llamadas de actualización de estado desde dentro del controlador de eventos están en funciones asíncronas o se activan debido a un código asíncrono, no se agruparán en lotes donde se agruparán las actualizaciones directas.
Donde sin el código de sincronización las actualizaciones de estado se realizan por lotes y las actualizaciones del código asíncrono no
function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
// async update from useEffect
useEffect(() => {
setTimeout(() => {
setCount1(count => count + 1);
setCount2(count => count + 2);
}, 3000);
}, []);
const handleAsyncUpdate = async () => {
await Promise.resolve("state updated");
setCount1(count => count + 2);
setCount2(count => count + 1);
};
const handleSyncUpdate = () => {
setCount1(count => count + 2);
setCount2(count => count + 1);
};
console.log("render", count1, count2);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button type="button" onClick={handleAsyncUpdate}>
Click for async update
</button>
<button type="button" onClick={handleSyncUpdate}>
Click for sync update
</button>
</div>
);
}
https://codesandbox.io/s/739rqyyqmq