React 19: useOptimistic с startTransition для лайков
-

React 19 принес нам несколько мощных инструментов для работы с асинхронными операциями. Два из них - useOptimistic и startTransition - идеально подходят для создания быстрых и отзывчивых интерфейсов, где пользователь видит результат своих действий мгновенно. В этой статье разберемся, как они работают вместе и почему это улучшает пользовательский опыт.
Вы когда-нибудь замечали, как в соцсетях лайк появляется сразу после клика, хотя запрос на сервер еще летит? Это оптимистичные обновления - техника, которая была сложной в реализации раньше. Теперь React дает нам встроенные инструменты для этого.
Что такое оптимистичные обновления
Оптимистичное обновление - это когда вы показываете результат действия пользователя в интерфейсе ещё до того, как получили подтверждение с сервера. Вместо того чтобы заставлять пользователя ждать, пока запрос дойдет до сервера и вернется с ответом, вы сразу обновляете UI так, как будто операция успешно завершилась.
Почему это важно? Человек привык видеть мгновенную реакцию на свои действия. Если вы поставили лайк и видите его сразу же, это создает ощущение отзывчивости приложения. Даже если сеть медленная или пользователь на мобильном интернете, интерфейс не будет зависать. Это улучшает воспринимаемую производительность, и приложение кажется намного быстрее.
Есть, конечно, риск: если запрос на сервер не пройдет или произойдет ошибка, нужно откатить изменения обратно. Раньше разработчики писали для этого много boilerplate кода, отслеживая состояния отправки, ошибки, откаты. React 19 упростил всё это благодаря useOptimistic.
useOptimistic - встроенное оптимистичное состояние
Хук useOptimistic создает отдельное состояние, которое показывает пользователю “оптимистичный” вариант данных. Он принимает текущее состояние и функцию редьюсера, которая описывает, как должны измениться данные при выполнении асинхронного действия.
Рассмотрим пример с сообщениями. Когда пользователь вводит текст в форму и нажимает отправить, вместо ожидания ответа от сервера, мы сразу добавляем сообщение в список с флагом “sending”. После успешной отправки флаг исчезает, и сообщение становится полноценным элементом данных.
const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [ { text: newMessage, sending: true }, ...state, ] );Второй аргумент - это функция, которая объясняет React, как обновить состояние оптимистично. Она получает текущее состояние (state) и новые данные (newMessage) и возвращает новое состояние с добавленным элементом.
Это работает следующим образом:
- Пользователь отправляет форму
- useOptimistic тут же обновляет состояние, добавляя новое сообщение
- Пользователь видит его в списке со статусом “отправляется”
- В фоне отправляется асинхронный запрос на сервер
- Когда ответ приходит, реальное состояние обновляется
- Сообщение теперь отображается без статуса отправки
startTransition - контроль приоритета обновлений
startTransition - это функция, которая помечает обновления состояния как менее приоритетные. Она говорит React: “Выполни эти обновления, но не блокируй более важные операции”. Это особенно полезно, когда вам нужно выполнить несколько изменений состояния за один раз.
Представьте, что при отправке сообщения вы хотите не только добавить его в список, но и очистить форму, обновить счетчик сообщений и выполнить другие операции. Без startTransition каждое изменение состояния вызовет отдельное обновление компонента, и UI будет перерисовываться несколько раз. С startTransition все эти обновления объединяются в один “переход”, что дает UI только одно обновление вместо нескольких.
вот как это выглядит в коде:
function formAction(formData) { addOptimisticMessage(formData.get("message")); formRef.current.reset(); startTransition(async () => { await sendMessageAction(formData); }); }Замечаете? Очищение формы происходит сразу (reset()), а асинхронная отправка обернута в startTransition. Это значит, что пользователь видит очищенную форму мгновенно, но асинхронный запрос выполняется в фоне с низким приоритетом.
Практический пример: лайки и комментарии
Теперь применим оба подхода к реальному случаю - лайкам и комментариям в социальной сети. Когда пользователь ставит лайк, мы хотим:
- Сразу показать, что лайк поставлен (сердечко закрасилось)
- Обновить счетчик лайков
- В фоне отправить запрос на сервер
- Если запрос не прошел, откатить изменения
Вот как это реализуется с useOptimistic и startTransition:
import { useOptimistic, startTransition } from "react"; function Post({ post, onLike }) { const [optimisticPost, addOptimisticLike] = useOptimistic( post, (state, liked) => ({ ...state, liked: liked, likeCount: liked ? state.likeCount + 1 : state.likeCount - 1 }) ); const handleLike = () => { const newLikedState = !optimisticPost.liked; addOptimisticLike(newLikedState); startTransition(async () => { await onLike(post.id, newLikedState); }); }; return ( <div> <button onClick={handleLike} style={{ color: optimisticPost.liked ? 'red' : 'gray' }} > ♥ {optimisticPost.likeCount} </button> </div> ); }В этом примере:
- Оптимистичный клик - пользователь кликает на лайк, и addOptimisticLike сразу обновляет состояние
- Визуальная обратная связь - цвет кнопки меняется на красный, счетчик увеличивается или уменьшается
- Фоновый запрос - в startTransition отправляется запрос на сервер
- Синхронизация - когда сервер подтверждает, состояние обновляется окончательно
Если запрос не пройдет (например, нет интернета или ошибка 500), useOptimistic автоматически откатит состояние к исходному значению.
Ключевые особенности и нюансы
Есть несколько важных моментов, которые нужно понимать при работе с этими инструментами:
startTransition с асинхронным кодом:
- Функция, переданная в startTransition, должна быть синхронной по сути, но может содержать асинхронные операции внутри
- Все обновления состояния, произойдущие во время выполнения функции, будут помечены как переходы
- Если вы обновляете состояние в setTimeout или Promise callback позже, это уже не будет частью перехода
Обработка ошибок:
- useOptimistic откатывает изменения автоматически, если асинхронная операция не пройдет
- Но вам нужно самостоятельно обработать ошибку и показать пользователю что-то вроде уведомления
- Рекомендуется использовать try-catch внутри startTransition для перехвата ошибок
Производительность:
- startTransition объединяет несколько обновлений в одно, что снижает количество перерисовок
- На быстрых устройствах это может быть незаметно, но на медленных девайсах разница будет существенной
- useOptimistic создает отдельное состояние, что немного увеличивает использование памяти, но обычно это не проблема
Работа с несколькими данными:
Сценарий Решение Преимущества Один элемент меняется useOptimistic достаточно Просто и быстро Несколько независимых изменений Несколько useOptimistic или один с объектом Гибкость Сложные каскадные обновления startTransition + несколько setState Полный контроль Формы с множеством полей startTransition обязателен Предотвращает блокировку UI Сравнение со старым подходом
Раньше, до React 19, разработчикам приходилось писать вот что-то вроде этого:
const [likes, setLikes] = useState(initialLikes); const [isLoading, setIsLoading] = useState(false); const [hasError, setHasError] = useState(false); const handleLike = async () => { const previousLikes = likes; setLikes(likes + 1); setIsLoading(true); try { await likeAPI(postId); setHasError(false); } catch (error) { setLikes(previousLikes); setHasError(true); } finally { setIsLoading(false); } };Много кода, множество состояний, легко допустить ошибку. С useOptimistic и startTransition это становится намного изящнее и надежнее.
Практические советы при использовании
Чтобы не наступить на грабли при работе с оптимистичными обновлениями, помните несколько простых правил:
- Завсегда оборачивайте асинхронный код в startTransition - это гарантирует, что UI не заблокируется, пока ждёт ответа от сервера
- Предусмотрите откат при ошибках - useOptimistic откатит состояние, но вам нужно уведомить пользователя о проблеме через toast или уведомление
- Избегайте сложной логики в редьюсере useOptimistic - функция, которую вы передаете вторым аргументом, должна быть предсказуемой и быстрой
- Тестируйте на медленных соединениях - используйте DevTools для throttle сети и убедитесь, что откаты работают правильно
- Не забывайте про ключи в списках - если отображаете оптимистичные элементы в списке, правильные ключи критически важны для React
Когда useOptimistic не поможет
Эти инструменты мощные, но не универсальные. Есть случаи, когда нужен другой подход:
- Если нужна реальная валидация на сервере перед показом - например, проверка доступности имени пользователя. Здесь нельзя показывать оптимистичный результат
- Если операция требует информации с сервера - например, генерация ID для нового элемента. Оптимистично показать его нельзя без ID
- Если нужна последовательность операций - когда одна операция зависит от результата другой, useOptimistic может не подойти
В этих случаях рекомендуется использовать useActionState или более сложные решения с управлением состояния.
Что еще важно знать о переходах
startTransition - это не просто обёртка для управления приоритетом. Это часть более широкой концепции “переходов” в React 19, которая связана с Concurrent Rendering - возможностью React прерывать длительные обновления если пришло что-то более важное.
Когда вы используете startTransition, React может приостановить выполнение обновления, если пользователь ввел текст в поле ввода или совершил другое действие. После выполнения более важного обновления, React вернется к прерванному переходу. Это создает ощущение, что приложение всегда отзывчиво, даже на медленных устройствах.
Также стоит помнить, что startTransition имеет асинхронную версию, которая позволяет работать с Promise и async-await без предупреждений React.
За кадром
При работе с оптимистичными обновлениями важно понимать границы между клиентским состоянием и серверным источником истины. useOptimistic - это инструмент для улучшения восприятия, а не для замены синхронизации с сервером. Всегда предполагайте, что запрос может не пройти, и готовьте откат. Также помните, что если у вас несколько клиентов обновляют один ресурс (например, несколько пользователей лайкают один пост), оптимистичные обновления должны быть устойчивыми к race condition. Правильная обработка ошибок и откатов - это не опция, а необходимость.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.