useOptimistic¶
useOptimistic - это React хук, позволяющий оптимистично обновлять пользовательский интерфейс.
1 | |
Описание¶
useOptimistic(value, reducer?)¶
Вызовите useOptimistic на верхнем уровне компонента, чтобы создать оптимистичное состояние для значения.
1 2 3 4 5 6 7 8 | |
Параметры¶
value: значение, которое возвращается, когда нет ожидающих действий.- необязательно
reducer(currentState, action): функция-редюсер, которая задаёт, как обновляется оптимистичное состояние. Она должна быть чистой, принимать текущее состояние и аргумент действия редюсера и возвращать следующее оптимистичное состояние.
Возвращаемое значение¶
useOptimistic возвращает массив ровно из двух значений:
optimisticState: текущее оптимистичное состояние. Оно равноvalue, если действие не ожидается. Если действие ожидается, оно равно состоянию, которое вернулreducer, или значению, переданному в функциюset, еслиreducerне передавали.- Функция
set, которой можно обновить оптимистичное состояние на другое значение внутри действия.
Функции set, например setOptimistic(optimisticState)¶
Функция set, которую возвращает useOptimistic, позволяет обновлять состояние на время действия. Можно передать следующее состояние напрямую или функцию, которая вычислит его из предыдущего:
1 2 3 4 5 6 7 8 9 10 | |
Параметры¶
optimisticState: значение, которое оптимистичное состояние должно иметь во время действия. Если вuseOptimisticпереданreducer, это значение уйдёт вторым аргументом в редюсер. Тип может быть любым.- Если в
optimisticStateпередать функцию, она будет считаться функцией обновления. Она должна быть чистой, принимать ожидающее состояние единственным аргументом и возвращать следующее оптимистичное состояние. React поставит функцию обновления в очередь и перерендерит компонент. При следующем рендере React вычислит следующее состояние, применив очередь обновлений к предыдущему состоянию, как у обновленийuseState.
- Если в
Возвращаемое значение¶
У функций set нет возвращаемого значения.
Предупреждения¶
- Функцию
setнужно вызывать внутри действия. Если вызвать сеттер вне действия, React покажет предупреждение, и оптимистичное состояние на мгновение отрендерится.
Как работает оптимистичное состояние
useOptimistic позволяет показать временное значение, пока действие ещё выполняется:
1 2 3 4 5 6 7 8 | |
Когда сеттер вызывают внутри действия, useOptimistic запускает повторный рендер, чтобы показать это состояние, пока действие идёт. Иначе возвращается value, переданное в useOptimistic.
Это состояние называют «оптимистичным», потому что им сразу показывают пользователю результат действия, хотя само действие ещё занимает время.
Как проходит обновление
-
Обновление сразу: когда вызывается
setOptimistic('b'), React сразу рендерит'b'. -
(Необязательно) await в действии: если в действии есть await, React продолжает показывать
'b'. -
Запланирован переход:
setValue(newValue)планирует обновление настоящего состояния. -
(Необязательно) ожидание Suspense: если
newValueприостанавливается, React продолжает показывать'b'. -
Один коммит рендера: в конце
newValueкоммитится и дляvalue, и дляoptimistic.
Лишнего рендера, чтобы «сбросить» оптимистичное состояние, нет. Оптимистичное и настоящее состояние сходятся в одном рендере, когда переход завершается.
Оптимистичное состояние временно
Оптимистичное состояние рендерится только пока действие выполняется, иначе рендерится value.
Если saveChanges вернула 'c', то и value, и optimistic будут 'c', а не 'b'.
Как определяется итоговое состояние
Аргумент value у useOptimistic определяет, что показывается после завершения действия. Как это работает, зависит от приёма:
-
Зашитые значения вроде
useOptimistic(false): после действияstateпо-прежнемуfalse, поэтому интерфейс показываетfalse. Это удобно для состояний ожидания, которые всегда начинаются сfalse. -
Переданные пропсы или состояние вроде
useOptimistic(isLiked): если родитель обновляетisLikedво время действия, после завершения используется новое значение. Так интерфейс отражает результат действия. -
Паттерн редюсера вроде
useOptimistic(items, fn): еслиitemsменяется, пока действие ожидает, React заново запускаетreducerс новымиitemsи пересчитывает состояние. Оптимистичные добавления остаются поверх свежих данных.
Что происходит, если действие завершается ошибкой
Если действие бросает ошибку, переход всё равно заканчивается, и React рендерит то, чем value является сейчас. Обычно родитель обновляет value только при успехе, поэтому при ошибке value не меняется, и интерфейс возвращается к тому, что было до оптимистичного обновления. Ошибку можно поймать и показать сообщение пользователю.
Использование¶
Оптимистичное состояние в компоненте¶
Вызовите useOptimistic на верхнем уровне компонента, чтобы объявить одно или несколько оптимистичных состояний.
1 2 3 4 5 6 7 | |
useOptimistic возвращает массив ровно из двух элементов:
- Оптимистичное состояние. Сначала оно равно переданному значению.
- Функция
set, которая на время действия временно меняет состояние. - Если передан редюсер, он выполнится перед возвратом оптимистичного состояния.
Чтобы использовать оптимистичное состояние, вызовите функцию set внутри действия.
Действия — это функции, которые вызывают внутри startTransition:
1 2 3 4 5 6 7 | |
Сначала React отрендерит оптимистичное состояние 42, а age останется текущим возрастом. Действие дождётся POST, а затем отрендерит newAge и для age, и для optimisticAge.
Подробный разбор — в разделе Как работает оптимистичное состояние.
Примечание
С пропами-действиями функцию set можно вызывать без startTransition:
1 2 3 4 | |
Это работает, потому что пропы-действия и так вызываются внутри startTransition.
Оптимистичное состояние в пропах-действиях¶
В пропе-действии оптимистичный сеттер можно вызвать напрямую, без startTransition.
В этом примере оптимистичное состояние выставляется внутри пропа submitAction у <form>:
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
1 2 3 4 | |
Когда пользователь отправляет форму, optimisticName сразу показывает newName, пока запрос к серверу ещё идёт. Когда запрос завершается, name и optimisticName рендерятся с настоящим updatedName из ответа.
Почему здесь не нужен startTransition?
По соглашению пропы, которые вызывают внутри startTransition, называют со словом «Action».
Раз submitAction назван со словом «Action», он уже вызывается внутри startTransition.
Паттерн пропа action описан в разделе Проп action у компонентов.
Оптимистичное состояние в пропах-действиях компонента¶
Когда вы создаёте проп-действие, можно добавить useOptimistic, чтобы сразу дать обратную связь.
Вот кнопка, которая показывает «Submitting...», пока action ожидает:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 2 3 | |
Когда кнопку нажимают, setIsPending(true) через оптимистичное состояние сразу показывает «Submitting...» и отключает кнопку. Когда действие заканчивается, isPending автоматически рендерится как false.
Так состояние ожидания показывается само, как бы проп action ни использовали вместе с Button:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Состояние ожидания будет видно, пока не закончится всё, что есть в пропе action.
Примечание
Состояние ожидания можно получить и через useTransition, по флагу isPending.
Разница в том, что useTransition даёт функцию startTransition, а useOptimistic работает с любым переходом. Берите то, что лучше подходит компоненту.
Оптимистичное обновление пропсов или состояния¶
Пропсы или состояние можно обернуть в useOptimistic, чтобы обновить их сразу, пока действие ещё выполняется.
В этом примере LikeButton получает isLiked пропом и сразу переключает его по клику:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
1 2 3 4 | |
Когда кнопку нажимают, setOptimisticIsLiked сразу меняет показанное состояние, и сердце выглядит нажатым. Тем временем в фоне выполняется await toggleLike. Когда await завершается, родительский setIsLiked обновляет «настоящее» состояние isLiked, и оптимистичное состояние рендерится в соответствии с этим новым значением.
Примечание
В примере следующее значение считается из optimisticIsLiked. Это работает, если базовое состояние не изменится. Если базовое состояние может измениться, пока действие ожидает, лучше функция обновления или редюсер.
Пример есть в разделе Обновление состояния на основе текущего состояния.
Обновление нескольких значений вместе¶
Если оптимистичное обновление затрагивает несколько связанных значений, обновляйте их вместе через редюсер. Тогда интерфейс остаётся согласованным.
Вот кнопка подписки, которая обновляет и состояние подписки, и число подписчиков:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
1 2 3 4 5 6 7 | |
Редюсер получает новое значение isFollowing и за одно обновление считает и новое состояние подписки, и новое число подписчиков. Текст кнопки и счётчик всегда остаются согласованными.
Как выбрать между функциями обновления и редюсерами
У useOptimistic два приёма, чтобы считать состояние от текущего:
Функции обновления работают как обновления useState. Передайте функцию в сеттер:
1 2 | |
Редюсеры отделяют логику обновления от вызова сеттера:
1 2 3 4 | |
Берите функции обновления, если вызов сеттера сам по себе описывает обновление. Это похоже на setState(prev => ...) у useState.
Берите редюсеры, если в обновление нужно передать данные (например, какой элемент добавить) или если один хук обрабатывает несколько типов обновлений.
Зачем редюсер?
Редюсеры нужны, когда базовое состояние может измениться, пока переход ожидает. Если todos изменится, пока добавление ещё ожидает (например, другой пользователь добавил задачу), React заново запустит редюсер с новыми todos и пересчитает, что показать. Новая задача добавится к свежему списку, а не к устаревшей копии.
Функция обновления вроде setOptimistic(prev => [...prev, newItem]) увидит только состояние на момент старта перехода и пропустит обновления, которые случились за время асинхронной работы.
Оптимистичное добавление в список¶
Чтобы оптимистично добавлять элементы в список, используйте reducer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
1 2 3 4 5 | |
reducer получает текущий список задач и новую задачу. Это важно: если проп todos изменится, пока добавление ожидает (например, другой пользователь добавил задачу), React обновит оптимистичное состояние, заново запустив редюсер с обновлённым списком. Новая задача добавится к свежему списку, а не к устаревшей копии.
Примечание
У каждого оптимистичного элемента есть флаг pending: true, чтобы показать загрузку у отдельных элементов. Когда сервер ответит и родитель обновит канонический список todos сохранённым элементом, оптимистичное состояние сменится на подтверждённый элемент без флага ожидания.
Несколько типов action¶
Если нужно несколько видов оптимистичных обновлений (например, добавление и удаление), используйте редюсер с объектами action.
В этом примере корзины один редюсер обрабатывает добавление и удаление:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
1 2 3 4 5 6 7 8 9 10 11 | |
Редюсер обрабатывает три типа action (add, remove, update_quantity) и для каждого возвращает новое оптимистичное состояние. Каждое action ставит флаг pending: true, чтобы показать обратную связь, пока выполняется серверная функция.
Оптимистичное удаление с восстановлением после ошибки¶
При оптимистичном удалении нужно обработать случай, когда действие завершается ошибкой.
В примере показано, как вывести сообщение об ошибке, если удаление не удалось: интерфейс сам откатывается и снова показывает элемент.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
1 2 3 4 5 6 7 | |
Попробуйте удалить «Deploy to production». Когда удаление не удастся, элемент сам появится в списке снова.
Оптимистическое обновление форм¶
Хук useOptimistic предоставляет возможность оптимистично обновлять пользовательский интерфейс до завершения фоновой операции, например, сетевого запроса. В контексте форм эта техника помогает сделать приложения более отзывчивыми. Когда пользователь отправляет форму, вместо того чтобы ждать, пока ответ сервера отразит изменения, интерфейс сразу же обновляется с ожидаемым результатом.
Например, когда пользователь вводит сообщение в форму и нажимает кнопку "Отправить", хук useOptimistic позволяет сообщению сразу же появиться в списке с надписью "Отправка...", еще до того, как оно будет отправлено на сервер. Такой "оптимистичный" подход создает впечатление скорости и оперативности. Затем форма пытается действительно отправить сообщение в фоновом режиме. Как только сервер подтверждает, что сообщение получено, метка "Отправка..." удаляется.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
1 2 3 4 | |
Устранение неполадок¶
Ошибка: «An optimistic state update occurred outside a Transition or Action»¶
Вы можете увидеть такую ошибку:
<ConsoleLogLine level="error">
An optimistic state update occurred outside a Transition or Action. To fix, move the update to an Action, or wrap with `startTransition`.
</ConsoleLogLine>
Оптимистичный сеттер нужно вызывать внутри startTransition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Если вызвать сеттер вне действия, оптимистичное состояние на мгновение появится и сразу вернётся к исходному значению. Так происходит, потому что нет перехода, который «удержал» бы оптимистичное состояние, пока выполняется действие.
Ошибка: «Cannot update optimistic state while rendering»¶
Вы можете увидеть такую ошибку:
<ConsoleLogLine level="error">
Cannot update optimistic state while rendering.
</ConsoleLogLine>
Эта ошибка возникает, если оптимистичный сеттер вызывают во время фазы рендера компонента. Его можно вызывать только из обработчиков событий, эффектов или других колбэков:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
Оптимистичные обновления показывают устаревшие значения¶
Если оптимистичное состояние как будто считается от старых данных, используйте функцию обновления или редюсер, чтобы считать его относительно текущего состояния.
1 2 3 4 5 6 7 | |
Подробности в разделе Обновление состояния на основе текущего состояния.
Непонятно, ожидает ли оптимистичное обновление¶
Чтобы узнать, ожидает ли useOptimistic, есть три варианта:
- Проверить
optimisticValue === value
1 2 | |
Если значения не равны, переход ещё идёт.
- Добавить
useTransition
1 2 3 4 5 6 7 | |
Поскольку useTransition под капотом использует useOptimistic для isPending, это то же самое, что вариант 1.
- Добавить флаг
pendingв редюсер
1 2 3 4 | |
У каждого оптимистичного элемента свой флаг, поэтому состояние загрузки можно показать у отдельных элементов.
Источник — https://react.dev/reference/react/useOptimistic