Vanilla JavaScript формы

Роль структурированной валидации в обработке форм

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

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

Библиотека Superstruct предоставляет минималистичный, но выразительный инструмент для описания структур данных и проверки соответствия входных значений этим структурам.


Базовая интеграция Superstruct в браузерной среде

Использование библиотеки в Vanilla JavaScript начинается с подключения и создания структуры формы. В среде без сборщика модулей возможна работа через CDN:

<script type="module">
  import { object, string, number, validate } from "https://cdn.skypack.dev/superstruct";
</script>

В модульной архитектуре структура данных формализуется отдельно от обработчиков событий.


Описание схемы формы

Форма регистрации пользователя может включать поля: имя, email и возраст. Структура данных описывается через композицию примитивов:

import { object, string, number, size, pattern } from "https://cdn.skypack.dev/superstruct";

const UserStruct = object({
  name: size(string(), 2, 30),
  email: pattern(string(), /^[^\s@]+@[^\s@]+\.[^\s@]+$/),
  age: number()
});

Каждый тип задаёт ограничение:

  • string() — строковое значение
  • number() — числовой тип
  • size() — ограничение длины строки
  • pattern() — регулярное выражение для проверки формата

Схема становится единым источником правил для формы.


Извлечение данных из HTML-формы

В Vanilla JavaScript данные формы извлекаются через объект FormData:

const form = document.querySelector("form");

function getFormData(form) {
  const data = new FormData(form);

  return {
    name: data.get("name"),
    email: data.get("email"),
    age: Number(data.get("age"))
  };
}

Преобразование типов выполняется вручную, поскольку HTML возвращает строки.


Валидация данных через Superstruct

Проверка соответствия структуры выполняется функцией validate:

import { validate } from "https://cdn.skypack.dev/superstruct";

function validateUser(data) {
  const [error, result] = validate(data, UserStruct);

  return { error, result };
}

Результат проверки имеет две составляющие:

  • error — объект ошибки при несоответствии структуры
  • result — валидированные данные при успешной проверке

Обработка события отправки формы

Интеграция в обработчик события submit позволяет контролировать поток данных до отправки:

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const data = getFormData(form);
  const { error, result } = validateUser(data);

  if (error) {
    console.log("Ошибка валидации:", error);
    return;
  }

  console.log("Валидные данные:", result);
});

Контроль отправки формы полностью переносится на уровень JavaScript-логики.


Работа с сообщениями об ошибках

Объект ошибки содержит подробную информацию о нарушении структуры:

if (error) {
  console.log(error.path);   // поле с ошибкой
  console.log(error.message); // описание ошибки
}

Для пользовательского интерфейса обычно требуется преобразование ошибок в удобный формат:

function formatErrors(error) {
  return error.failures().reduce((acc, failure) => {
    acc[failure.path[0]] = failure.message;
    return acc;
  }, {});
}

Результатом становится объект вида:

{
  email: "Expected a string matching pattern",
  age: "Expected a number"
}

Приведение типов перед валидацией

HTML-формы не гарантируют типизацию данных, поэтому предварительное преобразование критично.

function parseFormData(data) {
  return {
    name: String(data.name || ""),
    email: String(data.email || ""),
    age: Number(data.age)
  };
}

При отсутствии преобразования числовые проверки могут давать некорректные результаты.


Композиция сложных структур

Superstruct поддерживает вложенные объекты, что позволяет моделировать сложные формы.

const AddressStruct = object({
  city: string(),
  zip: string()
});

const ProfileStruct = object({
  name: string(),
  address: AddressStruct
});

Данные формы при этом должны соответствовать вложенной структуре:

{
  name: "Alex",
  address: {
    city: "Almaty",
    zip: "050000"
  }
}

Условная валидация

При необходимости проверки зависимых полей используется комбинация структур:

import { union } from "https://cdn.skypack.dev/superstruct";

const OptionalAge = union([number(), string()]);

Такая конструкция допускает несколько допустимых типов для одного поля.


Расширение через кастомные структуры

Создание пользовательских правил позволяет инкапсулировать бизнес-логику:

import { define } from "https://cdn.skypack.dev/superstruct";

const PositiveNumber = define("PositiveNumber", (value) => {
  return typeof value === "number" && value > 0;
});

Использование в схеме:

const ProductStruct = object({
  price: PositiveNumber
});

Синхронизация DOM и состояния валидации

Обновление интерфейса часто связывается с результатами проверки:

function showErrors(errors) {
  Object.entries(errors).forEach(([field, message]) => {
    const el = document.querySelector(`[data-error="${field}"]`);
    if (el) el.textContent = message;
  });
}

Очистка ошибок при повторной отправке:

function clearErrors() {
  document.querySelectorAll("[data-error]").forEach((el) => {
    el.textContent = "";
  });
}

Реактивная проверка полей

Проверка может выполняться не только при отправке формы, но и при вводе:

form.addEventListener("input", () => {
  const data = parseFormData(getFormData(form));
  const { error } = validateUser(data);

  if (error) {
    showErrors(formatErrors(error));
  } else {
    clearErrors();
  }
});

Такой подход обеспечивает постоянную синхронизацию состояния формы и схемы данных.


Инкапсуляция логики формы

Логика обработки формы может быть выделена в отдельный модуль:

function createFormHandler(struct, form, onSuccess) {
  return function handleSubmit(event) {
    event.preventDefault();

    const data = getFormData(form);
    const parsed = parseFormData(data);

    const { error, result } = validate(parsed, struct);

    if (error) {
      showErrors(formatErrors(error));
      return;
    }

    onSuccess(result);
  };
}

Такой подход позволяет переиспользовать валидацию для разных форм без дублирования кода.


Согласованность данных между клиентом и сервером

Использование одной схемы валидации на клиенте и сервере снижает вероятность рассинхронизации логики. Структура становится контрактом данных, а форма — интерфейсом этого контракта.

Валидационные схемы Superstruct остаются независимыми от DOM, что позволяет использовать их в любых слоях приложения без изменений.