Автоматическое дозирование в React 18
Основная команда React недавно выпустила альфа-версию React 18, которая включает готовые улучшения существующих функций, таких как:
- Автоматическое пакетирование для меньшего количества рендеров
- Поддержка SSR для Suspense
- Исправлены причуды неопределенного поведения.
В этом блоге мы поговорим о том, что такое пакетная обработка, как она работала раньше и что изменилось.
Что такое пакетная обработка?
Пакетная обработка – это когда React группирует несколько обновлений состояния в один повторный рендеринг для повышения производительности .
Давайте посмотрим на это на примере:
предположим, у нас есть два обновления состояния внутри обработчика кликов по событию. React объединит эти два обновления и отобразит только один раз.
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const clickHandler = () => {
setCount((count) => count + 1); // Does not re-render here
setToggle((toggle) => !toggle); // Does not re-render here
// React will only re-render once at the end(and that's batching)
};
console.log("Rendered ", count, toggle);
return (
<div>
<button onClick={clickHandler}>Click Me</button>
<h1>Count: {count}</h1>
<h1>Toggle: {toggle.toString()}</h1>
</div>
);
}
В приведенном выше примере представление будет отображаться только один раз после нажатия кнопки.
React выполняет автоматическое пакетирование для нас в случае обработчиков событий. Достаточно умен, чтобы объединить два обновления в одно, что приведет только к одному рендерингу и, следовательно, к повышению производительности приложения.
💻 Демо: пакетные обновления внутри обработчиков событий в React 17
Проблема с дозированием
React выполняет для нас автоматическое пакетирование, улучшая производительность приложения, и все идет хорошо.
НО, есть одна проблема. React несовместим с пакетными обновлениями.
Давайте посмотрим на пример:
предположим, нам нужно получить данные или использовать setTimeout, а затем обновить состояние в соответствии с результатом. Что ж, в этом случае React не будет пакетировать обновления и выполнять два независимых обновления состояния, следовательно, запускать два рендера.
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const clickHandler = () => {
setTimeout(() => {
setCount((count) => count + 1);
setToggle((toggle) => !toggle);
}, 100);
};
console.log("Rendered ", count, toggle);
return (
<div>
<button onClick={clickHandler}>Click Me</button>
<h1>Count: {count}</h1>
<h1>Toggle: {toggle.toString()}</h1>
</div>
);
}
В приведенном выше примере приложение будет повторно отрисовывать после, setCount
а затем снова после того, как в setToggle
результате будет выполнено два отрисовки.
💻 Демо: обновления внешних обработчиков событий НЕ пакетируются в React 17
До React 18 мы пакетировали обновления только во время обработчиков событий React. Обновления внутри обещаний, setTimeout, собственных обработчиков событий или любого другого события по умолчанию не пакетировались в React.
Решение
Основная команда React придумала идею автоматической пакетной обработки для React 18, что означает, что теперь все обновления будут автоматически пакетироваться , независимо от их происхождения. Итак, теперь обновления внутри тайм-аутов или сетевых вызовов будут обрабатываться так же, как и события.
Примечание. Ожидается, что вы установили React 18 Alpha и включили параллельный режим .
Давайте возьмем тот же пример и попробуем его с React 18:
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const clickHandler = () => {
setTimeout(() => {
setCount((count) => count + 1);
setToggle((toggle) => !toggle);
}, 100);
};
console.log("Rendered ", count, toggle);
return (
<div>
<button onClick={clickHandler}>Click Me</button>
<h1>Count: {count}</h1>
<h1>Toggle: {toggle.toString()}</h1>
</div>
);
}
Теперь, с React 18, оба обновления будут автоматически пакетироваться, и приложение будет отображаться только один раз.
💻 Демо: пакетные обновления вне обработчиков событий в React 18
Но подождите, а что, если вы не хотите выполнять пакетную обработку?
Бывают случаи, когда вы не захотите автоматически пакетировать обновления.
Что ж, на помощь приходит flushSync .
Мы можем использовать, ReactDOM.flushSync()
чтобы отказаться от пакетной обработки.
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const clickHandler = () => {
flushSync(() => {
setCount((count) => count + 1);
});
// React has updated the DOM by now
flushSync(() => {
setToggle((toggle) => !toggle);
});
// React has updated the DOM by now
};
console.log("Rendered ", count, toggle);
return (
<div>
<button onClick={clickHandler}>Click Me</button>
<h1>Count: {count}</h1>
<h1>Toggle: {toggle.toString()}</h1>
</div>
);
}
В приведенном выше коде мы использовали flushSync()
для обновления setCount
и setToggle
независимо друг от друга получили два рендера.
💻 Демонстрация: использование flushSync для независимого обновления состояний в React 18
TL; DR
До React 18 React пакетировал обновления только внутри обработчиков событий. Никакие другие обновления из обещаний, setTimeout или любого другого события не были пакетированы.
Начиная с React 18, react будет автоматически пакетировать все обновления, независимо от их происхождения.
Если вы хотите отказаться от пакетной обработки в React 18, вы можете добиться этого, используя flushSync()
.
Спасибо, что прочитали этот блог. Это мой первый блог, поэтому я очень нервничаю по поводу его публикации.