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

Проверка формы и восстановление ввода

Серверное действие закладки уже проверяет владельца, урок и допустимое состояние. Теперь читатель сможет изменить отображаемое имя. Это поле требует иной обратной связи: при ошибке важно сохранить ввод и объяснить, что исправить. Результат — форма, в которой серверная проверка сочетается с нативными ограничениями браузера.

Пример продолжает именованные действия кабинета. Репозиторий позднее получит setDisplayName, а загрузка вернёт безопасное имя пользователя. Не будем проверять настоящие документы или уникальность личности: отображаемое имя — подпись в учебном интерфейсе. Код и ответы не выполнялись; все показанные состояния ожидаемые.

Правило имени до разметки

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

Слишком строгий список букв исключил бы реальные варианты с дефисами, пробелами или письменностями, которых автор не предусмотрел. Поэтому не вводим регулярное выражение «только русские буквы». Вместо этого ограничим технически нежелательные символы и длину, оставив предметную свободу. Название поля должно соответствовать этой свободе: «Как показывать ваше имя» точнее, чем требование паспортного имени.

Фрагмент ниже добавляется в объект actions страницы account/+page.server.ts. repository и fail уже импортированы в девятом уроке. Добавьте метод setDisplayName(readerId, name) в контракт репозитория.

profile: async ({ request, locals }) => {
  if (!locals.reader) return fail(401, { message: 'Войдите в кабинет.' });
  const data = await request.formData();
  const raw = data.get('displayName');
  const value = typeof raw === 'string' ? raw.slice(0, 200) : '';
  const name = value.trim();
  if (name.length < 2 || name.length > 40 || /[\u0000-\u001f\u007f]/.test(name)) {
    return fail(400, {
      values: { displayName: value },
      errors: { displayName: 'Введите от 2 до 40 символов без управляющих знаков.' }
    });
  }
  await repository.setDisplayName(locals.reader.id, name);
  return { success: true, message: 'Имя обновлено.' };
}

Возвращаемое value ограничено ещё до ответа. Сервер не обязан отражать многомегабайтную строку обратно в HTML. Общий лимит тела запроса остаётся отдельной настройкой среды; slice применяется только после получения поля и не заменяет сетевую защиту.

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

Как ошибка попадает к полю

Компонент страницы получает form вместе с data через $props. data.reader.displayName — исходное серверное имя, form.values — ответ неудачного действия. Типы $types отражают форму этих данных после появления всех экспортов страницы.

<script lang="ts">
  import type { PageProps } from './$types';
  let { data, form }: PageProps = $props();
</script>
<form method="POST" action="?/profile">
  <label for="displayName">Как показывать ваше имя</label>
  <input id="displayName" name="displayName" required minlength="2" maxlength="40"
    value={form?.values?.displayName ?? data.reader.displayName}
    aria-invalid={form?.errors?.displayName ? 'true' : undefined}
    aria-describedby={form?.errors?.displayName ? 'name-error' : 'name-help'}>
  <p id="name-help">Подпись кабинета, до 40 символов.</p>
  {#if form?.errors?.displayName}
    <p id="name-error">{form.errors.displayName}</p>
  {/if}
  <button>Сохранить имя</button>
</form>

Это полный фрагмент формы, но он вставляется в существующую страницу с другими карточками и действиями. В файле остаётся один блок script; показанную декларацию объедините с прежними импортами. PageProps относится к текущему SvelteKit 2 API, а если конкретная установленная minor-версия его не экспортирует, используйте явные PageData и ActionData из того же $types.

При недопустимом имени ожидаем HTTP 400 с формой и текстом возле поля. При допустимом — новый ответ загрузки и сообщение об обновлении. Страница не должна заменяться общим экраном ошибки только потому, что человек ввёл короткую подпись: это ожидаемый результат действия, с которым можно продолжить работу.

Браузерная проверка и серверная проверка

required, minlength и maxlength помогают обычному вводу, но запрос способен прийти без браузера или с изменённой разметкой. Поэтому сервер повторяет предметное правило. Нативные атрибуты создают удобство, серверная проверка защищает целостность записи. Одна не заменяет другую.

Длина строки в JavaScript не всегда равна числу визуальных символов. Некоторые знаки и emoji состоят из нескольких кодовых единиц. Учебное правило использует string.length и явно ограничено этим представлением. Если продукт обещает сорок воспринимаемых символов, потребуется сегментация графем и согласованное поведение клиента и сервера.

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

Сообщение без раскрытия внутренних деталей

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

Содержание имени выводится обычной вставкой текста. Не используйте {@html} для «красивой подписи»: разрешённая длина не делает HTML безопасным. Браузерная разметка остаётся нашим шаблоном, пользовательское значение — данными. В таком разделении проверка и экранирование решают разные задачи.

Несколько форм на одной странице

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

Для более сложного кабинета полезно добавить к результату явный признак операции. Тогда сообщение и восстановление поля можно связать с конкретной формой. В нашем минимальном контракте используются отдельные поля errors и values, присутствующие только в ответе профиля; это ограничение видно в шаблоне.

Рассмотрите и параллельную отправку из двух вкладок. Последнее сохранённое имя станет актуальным по правилу учебного репозитория. Если нельзя терять предыдущую правку, понадобится версия записи в форме и ответ о конфликте. Сервер сравнивает эту версию с текущей после определения владельца; доверять переданной версии как доказательству доступа нельзя.

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

Официальная документация form actions объясняет передачу результата действия в form. Наши правила имени, сохранение ввода и связь ошибки с полем относятся к проектированию интерфейса «Маршрута». Следующий урок добавит ожидание отправки без потери обычного POST-пути и покажет, почему клиентское улучшение не должно менять смысл успешной записи.

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