Для компонентов класса this.setState вызывает пакетную обработку внутри обработчиков событий. Но что произойдет, если состояние будет обновлено вне обработчика событий и с помощью перехвата useState?
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>
}
Будет ли он сразу рендерить aa - bb? Или это будет aa - b, а потом aa - bb?





TL; DR - если изменения состояния запускаются асинхронно (например, завернутые в обещание), они не будут объединяться; если они запускаются напрямую, они будут группироваться.
Я установил песочницу, чтобы попробовать это: 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.info('a', a);
console.info('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);
Я сделал две кнопки, одна запускает изменения состояния, заключенные в обещание, как в вашем примере кода, другая запускает изменения состояния напрямую.
Если вы посмотрите на консоль, когда вы нажмете кнопку «с обещанием», она сначала покажет a aa и b b, затем a aa и b bb.
Итак, ответ - нет, в этом случае он не будет отображать aa - bb сразу, каждое изменение состояния запускает новый рендеринг, пакетной обработки нет.
Однако, когда вы нажмете кнопку «без обещания», консоль сразу покажет a aa и b bb.
Таким образом, в этом случае React выполняет пакетную обработку изменений состояния и выполняет один рендеринг для обоих вместе.
Примечание от github.com/facebook/react/issues/10231#issuecomment-31664495 0 - This is implementation detail and may change in future versions.
Я думаю, что проблема, на которую ссылается @Aprillion, не относится к хукам, это касается компонентов класса
@ned Хотя проблема была создана до хуков, сам комментарий применим к любой реализации состояния, приложения не должны полагаться на текущие детали оптимизации.
Но будут ли они выполнять по порядку?
буквы, используемые в этом примере, ужасно сбивают с толку. также правильно ли b b? Думаю, это опечатка.
@ Contemporary_user_name это не опечатка, когда вы обрабатываете щелчок с обещанием, изменения состояния не являются пакетными, поэтому при первом рендеринге значение b все еще равно «b», следовательно, оператор журнала «b b»
В настоящее время в React v16 и ранее по умолчанию пакетируются только обновления внутри обработчиков событий React, таких как click или onChange и т. д. Так же, как обновления состояния классов, подобным образом группируются в хуках.
Существует нестабильный API для принудительной пакетной обработки вне обработчиков событий в редких случаях, когда это необходимо.
ReactDOM.unstable_batchedUpdates(() => { ... })
Планируется, что в будущей версии все обновления состояния будут загружены при ответе, вероятно, версии 17 или выше.
Теперь также, если вызовы обновления состояния из обработчика событий находятся в асинхронных функциях или запускаются из-за асинхронного кода, они не будут пакетироваться, тогда как прямые обновления будут пакетироваться.
Если без кода синхронизации обновления состояния выполняются в пакетном режиме, а обновления асинхронного кода - нет.
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.info("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
Примечание от github.com/facebook/react/issues/10231#issuecomment-31664495 0 - This is implementation detail and may change in future versions.
изменения состояния внутри componentDidMount также группируются.
Насколько я могу судить, React 17.0.1 по-прежнему не выполняет пакетные обновления вне обработчиков событий React.
Если обработчик событий - react-based, он выполняет пакетные обновления. Это верно как для вызовов setState, так и для вызовов useState.
Но он не пакетируется автоматически, если событие основано на non-react, то есть setTimeout, вызовах Promise. Короче любое событие от Веб-API.
ответ уже дан @Patrick Hund .. Просто хочу здесь обновить, что с помощью React 18 пакетное обновление состояний возможно для Promise, также по умолчанию setTimeout.
Until React 18, we only batched updates during the React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default.
Ознакомьтесь с подробным объяснением. https://github.com/reactwg/react-18/discussions/21
Кстати, попробовал без Promise.resolve. setA и setB были объединены в пакет, как и ожидалось, аналогично компоненту класса (setState вызывается в обработчике событий).