Перейти к содержанию

Улучшение формы через use:enhance

Формы «Маршрута» уже определяют серверный путь: POST, проверка сессии, валидация, запись и результат. Теперь улучшим их поведение в браузере. Человек увидит ожидание отправки, а успешное действие обновит данные без обычной полной навигации. Серверный контракт при этом останется прежним.

Этот подход называется progressive enhancement: сначала работает базовая возможность браузера, затем клиент добавляет удобство. Нам важно сохранить первый путь, потому что JavaScript может загрузиться позже или не загрузиться вовсе. Используем enhance из $app/forms для SvelteKit 2. Приложение здесь не запускалось, поэтому ощущения от взаимодействия и сетевые результаты не проверены выполнением.

Базовая форма остаётся источником намерения

Не заменяйте форму на кнопку, которая вручную собирает JSON, если требуется только улучшить уже существующий POST. HTML хранит название метода, адрес действия и имена полей. Один и тот же набор можно отправить нативно или через клиентское улучшение. У сервера остаётся одна операция profile, а не два почти одинаковых обработчика.

В странице кабинета добавьте импорт enhance и локальное состояние ожидания. Фрагмент формы профиля из десятого урока получает use:enhance; остальные поля, тексты ошибок и текущие значения сохраняются.

<script lang="ts">
  import { enhance } from '$app/forms';
  let pending = $state(false);
  let notice = $state('');
</script>
<form method="POST" action="?/profile"
  use:enhance={() => {
    pending = true;
    notice = '';
    return async ({ update, result }) => {
      pending = false;
      await update({ reset: false });
      if (result.type === 'success') notice = 'Имя сохранено.';
      if (result.type === 'failure') notice = 'Проверьте поля формы.';
    };
  }}>
  <!-- Здесь остаются label, input и ошибка из предыдущего урока. -->
  <button disabled={pending}>{pending ? 'Сохраняем…' : 'Сохранить имя'}</button>
</form>
<p role="status">{notice}</p>

Показанный блок script объединяется с существующим, а комментарий обозначает место прежних полей. Для запуска нужно взять цельную страницу из конечных исходников или сохранить эти поля при редактировании. enhance применяется к POST-формам, отправляющимся в actions, и не является общим улучшением любой формы GET или произвольного внешнего endpoint. Назначение enhance описано в официальной документации.

Зачем нужен update

Функция, возвращённая из callback, получает результат серверного действия. Когда мы определяем собственную обработку, нужно явно сохранить стандартное обновление через update. Иначе локальная надпись может появиться, а загруженная история и form останутся прежними. Получится видимость успеха без соответствующего состояния страницы.

Опция reset: false сохраняет поля формы после результата. Для профиля это полезно: человек видит введённое имя и может продолжить исправление. Для создания новой заметки после успеха могло бы быть разумно очистить форму. Решение зависит от операции, поэтому не переносите эту настройку в любой POST без анализа.

Успех, ожидаемый отказ, перенаправление и неожиданная ошибка — разные результаты. В примере сообщения добавляются только для первых двух. update сохраняет обработку остальных типов. Не перехватывайте перенаправление на вход как ошибку имени и не рисуйте «Сохранено» после любого завершившегося сетевого обмена.

Ожидание относится к конкретной операции

pending существует в компоненте и означает незавершённую отправку текущей формы. Оно не хранится в профиле, не возвращается сервером и не отражает, занят ли пользователь вообще. Если на странице несколько независимых форм, одна общая переменная может заблокировать все кнопки при изменении одной закладки.

В конечном проекте простые формы закладок используют стандартный use:enhance, а профиль получает собственное ожидание. Это ограничение делает код короче и понятнее. Для массовых независимых действий понадобились бы отдельные идентификаторы ожидания и согласованная модель конкуренции, но три учебных урока такого усложнения не требуют.

Отключённая кнопка уменьшает вероятность двойного нажатия, однако не обеспечивает идемпотентность на сервере. Повтор способен прийти из другой вкладки, после сетевого тайм-аута или прямым запросом. Поэтому команда закладки по-прежнему передаёт желаемое состояние, а действие по-прежнему проверяет владельца. Клиентский комфорт и серверная целостность остаются отдельными слоями.

Сообщение, которое можно услышать

Статус размещён в существующем p с role="status". Когда текст изменится, вспомогательная технология сможет сообщить результат, не требуя вручную искать его на странице. Не следует объявлять каждый символ имени или каждый рендер формы как новое сообщение: статус относится к значимому результату отправки.

После ошибки поля должны оставаться доступными, а сообщение — связанным с конкретным вводом. Общая фраза «Проверьте поля» не заменяет текст возле имени. Если форма стала большой, можно добавить сводку ошибок со ссылками на поля. В «Маршруте» одно редактируемое имя, поэтому подробная инфраструктура сводки пока избыточна.

Фокус нельзя без необходимости перемещать на заголовок после каждой успешной закладки. Человек мог собираться снять другую отметку или продолжить чтение. При удалении карточки из подборки понадобится целевое восстановление фокуса, которое рассмотрим вместе с доступностью. Удобство обновления без полной навигации требует понимать, какой элемент остаётся после операции.

Два пути к одному результату

Без JavaScript браузер отправляет POST и получает обновлённую страницу. С JavaScript enhance перехватывает отправку, получает результат и применяет обновление. Оба пути должны завершиться одинаковой серверной записью. Разница находится в отображении ожидания и способе обновления, а не в правах пользователя.

Отправка недопустимого имени должна сохранить ввод и показать ошибку в обоих режимах. Отсутствующая сессия должна приводить к отказу в обоих режимах. Повтор установки закладки должен оставлять одну отметку. Эти ожидаемые проверки описывают контракт, но не являются проведёнными испытаниями.

Медленная сеть не меняет значение успеха

При медленной отправке ожидание остаётся на экране дольше. Это не повод автоматически показать успешную надпись через таймер. Таймер измеряет прошедшее время клиента, а не завершение записи на сервере. Для длительного запроса можно добавить пояснение о продолжающемся сохранении, сохранив исходный статус.

Если человек покидает страницу, отправленная команда могла уже дойти до сервера. Возврат в кабинет должен прочитать актуальное состояние, а не уверенно объявить операцию отменённой. Отмена браузерного ожидания и отмена серверного действия не являются одним механизмом.

Текущий пример не предоставляет кнопку отмены сохранения. Такое решение честнее, чем декоративная отмена, которая только закрывает индикатор. Если будущий продукт требует отзыв команды, серверному протоколу понадобится отдельная операция и понятная граница, после которой действие уже выполнено.

Сейчас форма готова к подключению реального учебного репозитория. Следующая глава создаст локальные сессии с непрозрачными токенами, заполнит locals и свяжет историю с серверным владельцем. Именно там прежняя демонстрационная страница станет кабинетом конкретного учебного читателя.

Оглавление серии