Обработка пользовательского ввода в веб-приложениях требует строгого
контроля структуры данных. HTML-формы предоставляют лишь базовый уровень
проверки, ограниченный атрибутами required,
pattern, min, max. При усложнении
логики эти механизмы становятся недостаточными, так как не обеспечивают
декларативного описания схем данных и повторного использования правил
валидации.
Структурная валидация решает задачу формализации входных данных через описание схемы. Такой подход позволяет отделить проверку данных от бизнес-логики и использовать единые правила как на стороне клиента, так и на стороне сервера.
Библиотека 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() — регулярное выражение для проверки
форматаСхема становится единым источником правил для формы.
В 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 возвращает строки.
Проверка соответствия структуры выполняется функцией
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
});
Обновление интерфейса часто связывается с результатами проверки:
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, что позволяет использовать их в любых слоях приложения без изменений.