Валидация на лету

Валидация в режиме изменения состояния опирается на идею немедленного пересчёта правил при каждом обновлении данных. Подход, реализуемый в Vest, строится вокруг декларативного описания проверок и их автоматического выполнения при изменении значений формы.

Ключевой принцип — привязка результата валидации не к моменту отправки данных, а к моменту их изменения. Это формирует модель, при которой каждое поле становится источником событий для системы проверки.

Основные характеристики:

  • пересчёт правил при каждом изменении значения
  • сохранение состояния ошибок по отдельным полям
  • группировка проверок по логическим сценариям
  • кэширование результатов выполнения тестов

Модель выполнения проверок

Внутри Vest логика валидации организована как набор сценариев (suite), содержащих тесты (test). Каждый тест представляет собой атомарное правило, которое либо проходит, либо возвращает ошибку.

Базовая структура:

import { create, test, enforce } from 'vest';

const validation = create((data = {}) => {
  test('username', 'Имя обязательно', () => {
    enforce(data.username).isNotEmpty();
  });

  test('email', 'Некорректный email', () => {
    enforce(data.email).matches(/.+@.+\..+/);
  });
});

Каждый вызов test фиксирует зависимость между полем и правилом. При повторном запуске изменяются только те тесты, которые зависят от обновлённых данных.

Механизм триггеров обновления

Валидация на лету строится на привязке функции проверки к обработчикам изменения состояния формы. Обычно это реализуется через события input или change.

let state = {
  username: '',
  email: ''
};

function onChange(field, value) {
  state[field] = value;
  validation(state);
}

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

Кэширование и оптимизация выполнения

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

Это даёт следующие эффекты:

  • снижение количества вычислений при частых вводах
  • ускорение реакции интерфейса
  • стабильность состояния ошибок при неизменных данных

Кэширование работает на уровне связки «поле — тест — результат», что позволяет точно определять область повторного выполнения.

Реактивная привязка к UI

Валидация на лету тесно связана с отображением состояния интерфейса. Результаты тестов используются для управления состоянием элементов формы.

Пример интеграции с UI-логикой:

const res = validation(state);

function getError(field) {
  return res.getErrors(field)[0];
}

Дальнейшая логика отображения строится вокруг состояния ошибок:

if (getError('email')) {
  showError('email', getError('email'));
}

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

Группировка правил и сценариев

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

const validation = create((data = {}) => {
  test('username', 'Обязательное поле', () => {
    enforce(data.username).isNotEmpty();
  });

  group('auth', () => {
    test('email', 'Email обязателен', () => {
      enforce(data.email).isNotEmpty();
    });

    test('password', 'Слишком короткий пароль', () => {
      enforce(data.password).longerThan(6);
    });
  });
});

Группировка позволяет:

  • логически разделять части формы
  • изолировать наборы правил
  • выполнять частичную валидацию

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

Одной из ключевых возможностей является выполнение тестов только при выполнении условий. Это снижает количество ненужных проверок и повышает предсказуемость результата.

test('company', 'Название компании обязательно', () => {
  if (data.isBusiness) {
    enforce(data.company).isNotEmpty();
  }
});

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

Обработка асинхронных проверок

Валидация на лету часто включает проверку данных на сервере: уникальность логина, доступность email и т.д. Асинхронные тесты интегрируются в общий поток выполнения.

test('username', 'Имя уже занято', async () => {
  const exists = await api.checkUsername(data.username);
  enforce(exists).isFalsy();
});

При этом система учитывает:

  • отмену устаревших запросов при новом вводе
  • сохранение порядка результатов
  • предотвращение конфликтов состояний

Инкрементальная переработка результатов

При каждом обновлении состояния происходит не полный пересчёт всех правил, а частичное обновление зависимых тестов.

Логика работы:

  • определяется изменённое поле
  • вычисляется набор затронутых тестов
  • выполняется только необходимый поднабор проверок
  • объединяются новые результаты с кэшированными

Такой подход критичен для больших форм с десятками полей.

Работа с зависимостями между полями

Часть правил зависит сразу от нескольких значений. Например, подтверждение пароля или логические зависимости между полями.

test('confirmPassword', 'Пароли не совпадают', () => {
  enforce(data.confirmPassword).equals(data.password);
});

Система отслеживает такие зависимости и автоматически повторяет тест при изменении любого из участвующих полей.

Управление состоянием результатов

Результат выполнения сценария представляет собой структурированный объект, содержащий ошибки, статус и метаданные выполнения.

Типичная структура:

{
  isValid: false,
  errors: {
    email: ['Некорректный email'],
    password: ['Слишком короткий пароль']
  }
}

Это состояние используется как единый источник истины для отображения интерфейса и управления логикой отправки данных.

Повторный запуск сценариев

При каждом изменении данных сценарий запускается повторно, но поведение остаётся детерминированным благодаря изоляции тестов.

validation.run(state);
validation.run(state);

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

Интеграция с архитектурой формы

Валидация на лету эффективно интегрируется в различные архитектурные подходы:

  • контролируемые формы
  • стор-ориентированные приложения
  • реактивные модели состояния

В каждом случае результат выступает промежуточным слоем между пользовательским вводом и бизнес-логикой обработки данных.

Поведение при частых изменениях состояния

При быстром вводе данных система должна сохранять стабильность. Для этого используются:

  • батчинг обновлений
  • приоритизация последних значений
  • отмена устаревших асинхронных запросов

Такая модель предотвращает «мигание» ошибок и нестабильное отображение состояния.

Структурирование сложных правил

При увеличении количества правил важным становится их композиция. Вместо монолитных проверок используются небольшие переиспользуемые функции.

const isRequired = (field) => {
  test(field, 'Обязательное поле', () => {
    enforce(field).isNotEmpty();
  });
};

Далее такие функции комбинируются внутри сценариев, формируя единый слой логики валидации.