Обработка событий формы

Базовая модель взаимодействия формы и событий

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

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

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

  • input — изменение значения в реальном времени
  • change — фиксация изменения значения
  • blur — потеря фокуса элементом
  • submit — попытка отправки формы

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


Связь событий с моделью валидации Vest

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

Типичная стратегия:

  • onChange → частичная валидация поля
  • onBlur → полная валидация конкретного поля
  • onSubmit → валидация всей формы

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

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

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

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

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

Привязка input событий к частичной валидации

Событие input возникает при каждом изменении значения поля. Оно обеспечивает максимальную отзывчивость, но требует аккуратного управления, чтобы избежать избыточных вычислений.

В связке с Vest обычно используется стратегия валидации только текущего поля.

Пример:

function handleInput(field, value, state) {
  state[field] = value;

  const result = validationSuite(state);

  const fieldErrors = result.getErrors(field);
  const fieldHasError = fieldErrors.length > 0;

  updateUI(field, fieldHasError ? fieldErrors[0] : null);
}

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


Обработка change событий как точка фиксации состояния

Событие change срабатывает реже, чем input, и чаще используется для:

  • select-элементов
  • чекбоксов и radio-групп
  • случаев, где важна фиксация значения

Валидация на change обычно применяется для полей, где промежуточные значения не имеют смысла.

Пример обработки:

function handleChange(field, value, state) {
  state[field] = value;

  const result = validationSuite(state);

  const errors = result.getErrors(field);

  renderFieldState(field, errors);
}

Здесь важно, что Vest не навязывает стратегию событийной обработки — она полностью определяется уровнем интеграции с UI.


Использование blur для контекстной валидации

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

Типичная стратегия:

  • не показывать ошибки на input
  • запускать валидацию при blur
  • использовать submit как финальную проверку

Пример:

function handleBlur(field, state) {
  const result = validationSuite(state);

  const errors = result.getErrors(field);

  setTouched(field, true);
  renderFieldState(field, errors);
}

Концепция touched состояния часто используется совместно с Vest для управления отображением ошибок. Поле считается «проверенным», если пользователь с ним взаимодействовал.


Обработка submit как централизованной точки проверки

Событие submit является финальной точкой валидационного процесса. Здесь выполняется полная проверка всей формы.

Ключевые особенности:

  • блокировка отправки при ошибках
  • запуск всех тестов Vest
  • агрегирование ошибок по полям

Пример:

function handleSubmit(event, state) {
  event.preventDefault();

  const result = validationSuite(state);

  if (result.hasErrors()) {
    const errors = result.getErrors();

    renderFormErrors(errors);
    return;
  }

  sendForm(state);
}

Vest здесь выступает как единый источник истины, определяющий корректность всей модели данных.


Декомпозиция состояния формы

При интеграции событий формы с Vest важно разделять:

  • значения формы (values)
  • состояние взаимодействия (touched/dirty)
  • результаты валидации (errors)

Такое разделение позволяет избежать смешивания UI-логики и бизнес-правил.

Пример структуры состояния:

const state = {
  values: {
    username: '',
    password: ''
  },
  touched: {
    username: false,
    password: false
  },
  errors: {}
};

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


Оптимизация повторных запусков валидации

Частый вызов suite на каждое событие может быть затратным. Для оптимизации применяются следующие подходы:

1. Локальная валидация

Запуск проверки только для конкретного поля:

result.getErrors('username');

2. Дебаунсинг input

Снижение частоты вызова:

const debouncedInput = debounce(handleInput, 200);

3. Кэширование состояния

Предотвращение повторной валидации при отсутствии изменений:

if (prevState[field] === value) return;

Синхронизация событий и результатов Vest

Vest возвращает структуру результатов, которая может быть использована напрямую для управления UI.

Основные методы:

  • result.hasErrors()
  • result.getErrors(field?)
  • result.tested(field)

Эти методы позволяют строить реактивную систему отображения ошибок, завязанную на события формы.

Пример синхронизации:

function updateUIFromResult(result) {
  Object.keys(state.values).forEach((field) => {
    const errors = result.getErrors(field);

    renderFieldState(field, errors);
  });
}

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

Комбинация событий форм и Vest позволяет реализовать несколько UX-стратегий:

Агрессивная валидация

  • input + мгновенная проверка
  • подходит для простых форм

Сбалансированная модель

  • input без ошибок
  • blur с отображением ошибок
  • submit как финальная проверка

Ленивый режим

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

Интеграция с кастомными UI-компонентами

При использовании компонентов (например, React-подобных абстракций или собственных UI-слоёв) события формы часто проксируются через единый обработчик.

Пример универсального обработчика:

function bindField(field) {
  return {
    onInput: (e) => handleInput(field, e.target.value, state),
    onBlur: () => handleBlur(field, state),
    onChange: (e) => handleChange(field, e.target.value, state)
  };
}

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


Обработка асинхронных событий и валидаций

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

Vest поддерживает асинхронные тесты, которые могут быть интегрированы в событийную модель:

test('username', 'Имя уже занято', async () => {
  await checkUsernameAvailability(data.username);
});

Обработка событий в этом случае требует учета:

  • состояния загрузки
  • блокировки submit
  • отображения промежуточного статуса

Разделение ответственности между UI и Vest

Ключевой архитектурный принцип заключается в том, что:

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

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