Работа с зависимыми и связанными данными в формах требует механизма, который позволяет описывать правила, зависящие не от одного поля, а от состояния нескольких значений одновременно. В экосистеме JavaScript такую задачу решает Vest — декларативная библиотека, ориентированная на композицию проверок, читаемость и управляемую валидацию сложных структур.
Связанные данные возникают в случаях, когда корректность одного поля определяется значением другого. Типичные сценарии:
Такие зависимости невозможно выразить через изолированные проверки отдельных полей без потери выразительности или дублирования логики.
Библиотека строится вокруг декларативного описания тестов (tests), которые группируются в сценарии (suite). Каждый тест получает доступ к значению текущего поля, а также к контексту всей формы.
Ключевой момент — наличие полного состояния данных внутри каждого валидатора:
import { create, test, enforce } from 'vest';
const suite = create((data) => {
test('password', 'Пароль обязателен', () => {
enforce(data.password).isNotEmpty();
});
test('confirmPassword', 'Пароли не совпадают', () => {
enforce(data.confirmPassword).equals(data.password);
});
});
Здесь видно, что проверка confirmPassword зависит от password, а значит валидация становится связанной по данным.
В отличие от классических валидаторов на уровне поля, подход Vest использует общий объект состояния.
Это приводит к следующим особенностям:
Пример зависимой логики:
const suite = create((data) => {
test('startDate', 'Некорректная дата начала', () => {
enforce(new Date(data.startDate)).lessThanOrEquals(new Date(data.endDate));
});
test('endDate', 'Некорректная дата окончания', () => {
enforce(new Date(data.endDate)).greaterThanOrEquals(new Date(data.startDate));
});
});
Обе проверки взаимно зависят друг от друга, образуя симметричную связь.
Одной из ключевых задач связанных данных является условная обязательность полей.
Пример: поле «VAT номер» обязательно только для компаний.
const suite = create((data) => {
test('companyName', 'Название компании обязательно', () => {
enforce(data.companyName).isNotEmpty();
});
test('vatNumber', 'VAT обязателен для компаний', () => {
if (data.isCompany) {
enforce(data.vatNumber).isNotEmpty();
}
});
});
Здесь зависимость выражена через флаг isCompany, который влияет на поведение другой проверки.
Такой подход позволяет:
Связанные данные часто проявляются в массивах или вложенных структурах. Например, корзина товаров должна соответствовать бизнес-ограничениям.
const suite = create((data) => {
test('items', 'Корзина не может быть пустой', () => {
enforce(data.items.length).greaterThan(0);
});
test('items', 'Сумма не должна превышать лимит', () => {
const total = data.items.reduce((sum, item) => sum + item.price, 0);
enforce(total).lessThanOrEquals(data.userLimit);
});
});
Здесь данные массива связаны с внешним контекстом пользователя. Любое изменение одного элемента влияет на общий результат.
В сложных формах изменения одного поля могут инициировать цепочку пересчётов.
Пример: выбор страны влияет на регион, а регион — на доступные города.
const suite = create((data) => {
test('country', 'Страна обязательна', () => {
enforce(data.country).isNotEmpty();
});
test('region', 'Регион не соответствует стране', () => {
enforce(data.region).inside(getRegionsByCountry(data.country));
});
test('city', 'Город не принадлежит региону', () => {
enforce(data.city).inside(getCitiesByRegion(data.region));
});
});
Зависимости образуют направленный граф:
country → region → city
Изменение одного узла влияет на последующие проверки.
В Vest допускается динамическая генерация правил внутри тестов. Это важно для связанных данных, где ограничения зависят от вычисляемого состояния.
const suite = create((data) => {
test('discountCode', 'Недействительный код', () => {
const allowedCodes = data.isPremium
? ['PREMIUM10', 'VIP20']
: ['WELCOME5'];
enforce(data.discountCode).inside(allowedCodes);
});
});
Здесь логика валидации полностью определяется текущим состоянием формы.
При росте количества связанных полей возникает необходимость выносить зависимости в отдельные вычисляемые функции:
const isAdult = (data) => data.age >= 18;
const hasConsent = (data) => data.parentConsent === true;
const suite = create((data) => {
test('age', 'Возраст некорректен', () => {
enforce(data.age).greaterThan(0);
});
test('parentConsent', 'Требуется согласие', () => {
if (!isAdult(data)) {
enforce(hasConsent(data)).isTruthy();
}
});
});
Такой подход уменьшает связность тестов и делает зависимости явными на уровне функций.
Перекрёстные ограничения возникают, когда два поля должны удовлетворять общей бизнес-логике.
Пример: кредитный лимит и ежемесячный платёж:
const suite = create((data) => {
test('monthlyPayment', 'Платёж слишком высок', () => {
enforce(data.monthlyPayment).lessThanOrEquals(data.income * 0.4);
});
test('creditLimit', 'Лимит не соответствует доходу', () => {
enforce(data.creditLimit).lessThanOrEquals(data.income * 10);
});
test('combinedRisk', 'Суммарная нагрузка превышает допустимую', () => {
const risk = data.monthlyPayment * 12 + data.creditLimit * 0.1;
enforce(risk).lessThanOrEquals(data.income * 3);
});
});
Здесь каждое поле проверяется отдельно, но финальная проверка учитывает их комбинацию.
Механизм работы Vest позволяет оптимизировать пересчёт: при изменении одного поля пересчитываются только затронутые тесты.
Это особенно важно для связанных данных:
Так формируется частичная реактивность без необходимости полного пересчёта всей формы.
Связанные данные часто представлены в виде вложенных объектов:
const suite = create((data) => {
test('user.profile.email', 'Некорректный email', () => {
enforce(data.user.profile.email).matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
test('user.settings.notifications', 'Некорректная настройка', () => {
if (data.user.settings.emailNotifications) {
enforce(data.user.profile.email).isNotEmpty();
}
});
});
Здесь зависимости пересекают уровни вложенности, что требует доступа к полному дереву данных.
Главная цель работы с зависимыми данными заключается не в проверке отдельных значений, а в обеспечении согласованности всей структуры.
В рамках Vest это выражается через:
Так формируется единая модель целостности данных, в которой форма рассматривается как система взаимосвязанных ограничений.