Ленивая валидация

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

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

Ленивая стратегия в Vest выражается через:

  • выполнение тестов только для «затронутых» полей;
  • пропуск незадействованных правил;
  • повторный запуск только при изменении состояния поля;
  • возможность управлять моментом активации проверки.

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

Принцип работы ленивых тестов

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

Ленивая валидация опирается на внутреннее состояние выполнения:

  • какие поля уже были изменены;
  • какие тесты уже запускались;
  • какие ошибки актуальны;
  • какие зависимости затронуты текущим изменением.

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

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

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

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

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

Триггерная модель выполнения

Ленивая валидация в Vest обычно строится вокруг событийной модели:

  • изменение значения поля;
  • потеря фокуса (blur);
  • явный submit;
  • программный вызов валидации.

Изменение конкретного поля

validation({ username: 'alex' }).getErrors('username');

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

Отложенная активация

Часто применяется стратегия «не валидировать до взаимодействия»:

let touched = {};

const onCha nge = (field, value) => {
  touched[field] = true;

  validation({ [field]: value })
    .getErrors(field);
};

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

Состояние полей и ленивое выполнение

Ключевой элемент ленивой валидации — состояние «затронутости» (touched). Vest не навязывает его напрямую, но архитектурно предполагает его использование на уровне приложения.

Типичные состояния:

  • untouched — поле не изменялось;
  • touched — пользователь взаимодействовал;
  • dirty — значение отличается от начального;
  • validated — уже проверено.

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

Пример интеграции состояния

const state = {
  touched: {},
};

function setValue(field, value) {
  state.touched[field] = true;

  const result = validation({ [field]: value });

  if (state.touched[field]) {
    return result.getErrors(field);
  }
}

Кеширование результатов

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

Vest способен повторно использовать результаты предыдущих тестов, если:

  • входные данные не изменились;
  • зависимые поля не затронуты;
  • контекст выполнения совпадает.

Это позволяет избегать повторного выполнения дорогостоящих проверок.

Пример поведения кеша

Если email уже был проверен и значение не изменилось:

validation({ email: 'test@mail.com' }).getErrors('email');

повторный вызов не приведёт к повторному выполнению regex-проверки, если состояние сьюта не изменилось.

Ленивые зависимости между полями

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

Пример зависимости

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

При изменении password необходимо пересчитать только зависимые тесты, а не всю форму целиком.

Ленивая стратегия здесь включает:

  • отслеживание зависимостей;
  • пересчёт только связанных тестов;
  • игнорирование независимых полей.

Частичная валидация (field-level execution)

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

validation(data).getErrors('email');

Это эквивалент «точечного» запуска тестов:

  • выполняются только тесты с меткой email;
  • остальные тесты не активируются;
  • результат возвращается изолированно.

Оптимизация через условные тесты

Vest поддерживает условное выполнение тестов, что усиливает ленивую стратегию.

test('age', 'Недопустимый возраст', () => {
  if (!data.age) return;

  enforce(data.age).greaterThan(18);
});

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

Ленивые сценарии в реальных формах

В реальных интерфейсах ленивый подход применяется в следующих сценариях:

  • формы регистрации с множеством полей;
  • динамические формы с условными блоками;
  • шаговые (wizard) интерфейсы;
  • формы с асинхронной проверкой.

Шаговые формы

На каждом шаге активируется только часть схемы:

const step1 = validation({ username, email }).getErrors();
const step2 = validation({ password }).getErrors();

Это естественная форма ленивой валидации, где остальная логика «спит» до перехода на следующий шаг.

Асинхронная ленивость

Ленивая валидация особенно важна при асинхронных проверках:

  • проверка уникальности email;
  • запросы к серверу;
  • внешние API.
test('email', 'Email уже используется', async () => {
  const exists = await api.checkEmail(data.email);
  enforce(exists).equals(false);
});

В ленивом режиме такой тест выполняется только при изменении email, а не при любом изменении формы.

Управление пересчётом

Контроль момента пересчёта является ядром ленивой стратегии.

Основные подходы:

  • запуск только на blur;
  • запуск на submit;
  • debounce на input;
  • ручной trigger.
const onB lur = () => {
  validation(data).getErrors();
};

Снижение стоимости валидации

Ленивая валидация снижает нагрузку за счёт:

  • уменьшения количества выполняемых тестов;
  • исключения повторных проверок;
  • изоляции изменений;
  • кеширования результатов;
  • приоритизации активных полей.

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