Partial validation

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

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

Основная цель частичной валидации:

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

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

Структура валидационных наборов и область выполнения

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

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

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

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

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

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

Механизм частичного запуска

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

Типичный сценарий — обновление одного поля формы:

suite({ username: 'john' }, 'username');

В этом случае выполняются только тесты, связанные с username, остальные блоки игнорируются.

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

Группировка проверок

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

Пример структуры:

const suite = create((data = {}) => {
  test('account.username', () => {
    enforce(data.account.username).isNotEmpty();
  });

  test('account.email', () => {
    enforce(data.account.email).matches(/.+@.+\..+/);
  });

  test('profile.firstName', () => {
    enforce(data.profile.firstName).isNotEmpty();
  });

  test('profile.lastName', () => {
    enforce(data.profile.lastName).isNotEmpty();
  });
});

Запуск проверки может ограничиваться целой веткой:

suite(data, 'account');

или более узко:

suite(data, 'profile.firstName');

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

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

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

Модель поведения:

  1. Пользователь изменяет значение поля
  2. Определяется ключ изменённого поля
  3. Запускается валидация только этого ключа
  4. Результат обновляет состояние ошибок локально
function onFieldChange(field, value) {
  setFormState(prev => ({
    ...prev,
    [field]: value
  }));

  const result = suite({ [field]: value }, field);
  setErrors(prev => ({
    ...prev,
    [field]: result.getErrors(field)
  }));
}

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

Изоляция зависимых правил

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

test('passwordConfirmation', () => {
  enforce(data.passwordConfirmation).equals(data.password);
});

При изменении password требуется также повторная проверка passwordConfirmation. В таких сценариях применяется расширение области выполнения:

suite(data, ['password', 'passwordConfirmation']);

или группировка зависимых полей в один контекст.

Асинхронные проверки и частичное выполнение

Асинхронные правила особенно чувствительны к частичной валидации. Запросы к серверу (например, проверка уникальности email) должны выполняться только при изменении соответствующего поля.

test('email', async () => {
  await enforce(data.email).isNotEmpty();

  const exists = await checkEmailExists(data.email);
  enforce(exists).isFalsy();
});

При частичном запуске такие проверки полностью изолируются:

suite(data, 'email');

Это предотвращает избыточные сетевые запросы при изменении других частей формы.

Оптимизация через кэширование результатов

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

Логика строится вокруг сравнения предыдущего состояния и текущего значения:

if (prevState.email === nextState.email) {
  return prevResult.email;
}

Такой подход особенно полезен при частых перерендерах интерфейса.

Ограничения частичной валидации

Несмотря на эффективность, частичная проверка имеет ограничения:

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

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

Детерминированность результатов при частичном запуске

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

Это достигается за счёт того, что:

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

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