Объединение и регистрация валидации
Сборка всех валидаторов и интеграция с формой.
Обзор
Мы создали валидаторы для каждого шага плюс валидацию между шагами. Теперь давайте:
- Создадим основной файл валидации, который объединяет всё
- Зарегистрируем валидацию с формой
- Протестируем что вся валидация работает вместе
- Проверим полную структуру файлов
Создание основного файла валидации
Создайте основной файл валидации, который импортирует и применяет все валидаторы шагов:
touch src/schemas/validators/credit-application.ts
Реализация
import type { ValidationSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';
// Импортируйте валидаторы шагов
import { loanValidation } from './loan-info';
import { personalValidation } from './personal-info';
import { contactValidation } from './contact-info';
import { employmentValidation } from './employment';
import { additionalValidation } from './additional-info';
import { crossStepValidation } from './cross-step';
/**
* Полная схема валидации для формы кредитного заявления
*
* Организована по шагам формы для поддерживаемости:
* - Шаг 1: Информация о кредите
* - Шаг 2: Личная информация
* - Шаг 3: Контактная информация
* - Шаг 4: Занятость
* - Шаг 5: Дополнительная информация
* - Между шагами: Валидация, охватывающая несколько шагов
*/
export const creditApplicationValidation: ValidationSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// Шаг 1: Информация о кредите
// ==========================================
loanValidation(path);
// ==========================================
// Шаг 2: Личная информация
// ==========================================
personalValidation(path);
// ==========================================
// Шаг 3: Контактная информация
// ==========================================
contactValidation(path);
// ==========================================
// Шаг 4: Занятость
// ==========================================
employmentValidation(path);
// ==========================================
// Шаг 5: Дополнительная информация
// ==========================================
additionalValidation(path);
// ==========================================
// Валидация между шагами
// ==========================================
crossStepValidation(path);
};
Регистрация с формой
Обновите функцию создания формы для включения валидации:
import { createForm } from '@reformer/core';
import { creditApplicationSchema } from './credit-application.schema';
import { creditApplicationBehaviors } from '../behaviors/credit-application.behaviors';
import { creditApplicationValidation } from '../validators/credit-application';
import type { CreditApplicationForm } from '@/types';
export function createCreditApplicationForm() {
return createForm<CreditApplicationForm>({
schema: creditApplicationSchema,
behaviors: creditApplicationBehaviors,
validation: creditApplicationValidation, // ← Зарегистрируйте валидацию здесь
});
}
Вот и всё! Валидация теперь активна при создании формы.
Структура файлов
Ваш проект должен иметь теперь эту структуру:
src/
├── schemas/
│ ├── validators/
│ │ ├── loan-info.ts
│ │ ├── personal-info.ts
│ │ ├── contact-info.ts
│ │ ├── employment.ts
│ │ ├── additional-info.ts
│ │ ├── cross-step.ts
│ │ └── credit-application.ts ← Основной файл
│ ├── behaviors/
│ │ ├── loan-info.ts
│ │ ├── personal-info.ts
│ │ ├── contact-info.ts
│ │ ├── employment.ts
│ │ ├── additional-info.ts
│ │ ├── cross-step.behaviors.ts
│ │ └── credit-application.behaviors.ts
│ ├── credit-application.ts
│ └── create-form.ts ← Валидация зарегистрирована здесь
│
├── components/
│ ├── forms/
│ │ └── createCreditApplicationForm.ts
│ ├── steps/
│ ├── nested-forms/
│ └── CreditApplicationForm.tsx
│
└── types/
└── credit-application.ts
Тестирование всей валидации
Создайте полный контрольный список тестирования:
Шаг 1: Информация о кредите
- Обязательные поля (loanType, loanAmount, loanTerm, loanPurpose)
- Числовые диапазоны (сумма 50k-10M, срок 6-360 месяцев)
- Длина строки (цель 10-500 символов)
- Условные поля ипотеки (propertyValue, initialPayment)
- Условные поля автокредита (марка, модель, год, цена)
Шаг 2: Личная информация
- Требуемые имена с паттерном кириллицы
- Дата рождения не в будущем
- Валидация возраста 18-70
- Серия паспорта (4 цифры) и номер (6 цифр)
- Валидация даты выдачи паспорта
- ИНН (10 или 12 цифр) и СНИЛС (11 цифр)
Шаг 3: Контактная информация
- Требуемые главный телефон и email
- Опциональный дополнительный телефон и email (валидируются если указаны)
- Требуемые поля адреса регистрации
- Условный адрес проживания (когда sameAsRegistration = false)
- Формат почтового кода (6 цифр)
Шаг 4: Занятость
- Требуемый статус занятости
- Требуемый ежемесячный доход (min 10 000)
- Условные поля компании (при работе)
- Стаж работы >= 3 месяцев (при работе)
- Условные поля бизнеса (при самозанятости)
- Опыт бизнеса >= 6 месяцев (при самозанятости)
Шаг 5: Дополнительная информация
- Массив имущества (min 1 когда hasProperty, max 10)
- Валидация элемента имущества (тип, описание, стоимость)
- Массив существующих кредитов (min 1 когда hasExistingLoans, max 20)
- Валидация элемента кредита (банк, сумма, платёж)
- Массив созаёмщиков (min 1 когда hasCoBorrower, max 5)
- Валидация элемента созаёмщика (имя, email, телефон, доход)
Между шагами
- Первоначальный платёж >= 20% стоимости имущества
- Ежемесячный платёж <= 50% дохода домохозяйства
- Сумма кредита <= цена автомобиля
- Остаток кредита <= оригинальный кредит
- Валидация возраста на вычисляемом поле
- Асинхронная проверка ИНН (показывает загрузку, валидирует)
- Асинхронная проверка СНИЛС
- Асинхронная проверка уникальности email
Отладка валидации
Если валидация не работает как ожидается:
1. Проверьте консоль на ошибки
// Добавьте debug логирование в валидаторы
export const loanValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
console.log('Регистрирую валидацию Шага 1');
required(path.loanAmount, { message: 'Сумма кредита обязательна' });
console.log('Добавил валидатор required для loanAmount');
};
2. Проверьте пути полей
Неправильные пути полей вызывают молчащий отказ валидации:
// ❌ Неправильно - опечатка в имени поля
required(path.loanAmmount, { message: '...' });
// ✅ Правильно
required(path.loanAmount, { message: '...' });
3. Проверьте регистрацию формы
Убедитесь что валидация передана в createForm:
// ❌ Забыли добавить валидацию
createForm({
schema: creditApplicationSchema,
behaviors: creditApplicationBehaviors,
});
// ✅ Валидация зарегистрирована
createForm({
schema: creditApplicationSchema,
behaviors: creditApplicationBehaviors,
validation: creditApplicationValidation,
});
4. Проверьте интеграцию компонента
Убедитесь что вы используете форму с валидацией:
function CreditApplicationForm() {
const form = useMemo(() => createCreditApplicationForm(), []); // ← Использует валидацию
return <FormField control={form.loanAmount} />;
}
5. Проверьте статус поля
Отлаживайте состояние валидации поля:
function DebugField({ control }: { control: FieldNode<any> }) {
const errors = control.errors.value;
const isValid = control.isValid.value;
const isValidating = control.isValidating.value;
console.log('Ошибки поля:', errors);
console.log('Валидно:', isValid);
console.log('Валидируется:', isValidating);
return <FormField control={control} />;
}
Порядок выполнения валидации
Понимание когда запускается валидация:
1. При изменении поля
form.field('loanAmount').setValue(100000);
// → Срабатывают все валидаторы для loanAmount
// → Срабатывают валидаторы которые зависят от loanAmount
2. При изменении зависимости
form.field('loanType').setValue('mortgage');
// → Переиспускаются условные валидаторы:
// - requiredWhen для propertyValue
// - requiredWhen для initialPayment
// - Валидатор между шагами первоначального платежа
3. При отправке формы
form
.submit()
.then((data) => {
console.log('Валидные данные:', data);
})
.catch((errors) => {
console.log('Ошибки валидации:', errors);
});
4. Ручная валидация
// Валидируйте одно поле
await form.field('loanAmount').validate();
// Валидируйте всю форму
await form.validate();
// Валидируйте определённый шаг
await form.group('step1').validate();
Соображения производительности
Валидация оптимизирована в ReFormer, но учитывайте это:
1. Избегайте дорогостоящих синхронных валидаторов
// ❌ Плохо - дорогостоящая операция при каждом изменении
createValidator(path.field, [], (value) => {
return expensiveCalculation(value); // Запускается при каждом нажатии клавиши!
});
// ✅ Лучше - держите синхронные валидаторы быстрыми
createValidator(path.field, [], (value) => {
return quickCheck(value);
});
2. Используйте debouncing для асинхронных валидаторов
// ❌ Плохо - API вызов при каждом нажатии клавиши
createAsyncValidator(path.inn, async (inn) => {
return await fetch(`/api/validate/inn?value=${inn}`);
});
// ✅ Хорошо - debounced API вызовы
createAsyncValidator(
path.inn,
async (inn) => {
return await fetch(`/api/validate/inn?value=${inn}`);
},
{ debounce: 500 } // ← Debounce
);
3. Минимизируйте зависимости
// ❌ Плохо - ненужные зависимости
createValidator(
path.field,
[path.a, path.b, path.c, path.d, path.e], // Слишком много!
(value, deps) => {
/* ... */
}
);
// ✅ Хорошо - только необходимые зависимости
createValidator(
path.field,
[path.dependency], // Только что нужно
(value, [dep]) => {
/* ... */
}
);
4. Не создавайте циклические зависимости
// ❌ Плохо - циклическая зависимость
createValidator(path.a, [path.b], (a, [b]) => {
/* ... */
});
createValidator(path.b, [path.a], (b, [a]) => {
/* ... */
}); // Бесконечный цикл!
// ✅ Хорошо - одностороннее зависимости
createValidator(path.a, [], (a) => {
/* ... */
});
createValidator(path.b, [path.a], (b, [a]) => {
/* ... */
});
Доступ к состоянию валидации
В компонентах
import { useField } from '@reformer/core/react';
function FormField({ control }) {
const field = useField(control);
return (
<div>
<input value={field.value ?? ''} onChange={(e) => control.setValue(e.target.value)} />
{/* Показать ошибки */}
{field.errors.length > 0 && (
<div className="error">
{field.errors.map((error) => (
<div key={error.type}>{error.message}</div>
))}
</div>
)}
{/* Показать загрузку для асинхронной валидации */}
{field.isValidating && <span>Валидируется...</span>}
</div>
);
}
В логике формы
const form = createCreditApplicationForm();
// Проверьте если форма валидна
const isValid = form.isValid.value;
// Получите все ошибки
const errors = form.errors.value;
// Проверьте определённое поле
const loanAmountErrors = form.field('loanAmount').errors.value;
// Подпишитесь на изменения валидации
form.isValid.subscribe((valid) => {
console.log('Форма валидна:', valid);
});
Резюме
Мы успешно реализовали полную валидацию для формы кредитного заявления:
Шаг 1: Информация о кредите
- ✅ Требуемые поля и числовые диапазоны
- ✅ Валидация длины строки
- ✅ Условные поля ипотеки/автокредита
Шаг 2: Личная информация
- ✅ Валидация имён с паттерном кириллицы
- ✅ Валидация даты рождения и возраста
- ✅ Валидация формата паспорта
- ✅ Паттерны ИНН и СНИЛС
Шаг 3: Контактная информация
- ✅ Валидация формата email и телефона
- ✅ Требуемые поля адреса
- ✅ Условный адрес проживания
Шаг 4: Занятость
- ✅ Требуемые доход и статус
- ✅ Условные поля занятости
- ✅ Условные поля самозанятости
- ✅ Минимумы стажа/опыта
Шаг 5: Дополнительная информация
- ✅ Валидация длины массива
- ✅ Валидация элементов массива
- ✅ Валидация вложенных объектов в массивах
Между шагами
- ✅ Валидация первоначального платежа >= 20%
- ✅ Ежемесячный платёж <= 50% дохода
- ✅ Сумма кредита <= цена автомобиля
- ✅ Остаток кредита <= оригинальная сумма
- ✅ Валидация возраста
- ✅ Асинхронная проверка ИНН
- ✅ Асинхронная проверка СНИЛС
- ✅ Асинхронная проверка уникальности email
Ключевые достижения
- Декларативная валидация - Ясные, поддерживаемые определения валидации
- Организованная структура - Легко найти и изменить валидаторы
- Типобезопасность - Полная поддержка TypeScript
- Условная логика - Динамическая валидация на основе состояния формы
- Между полями - Сложные бизнес-правила между несколькими полями
- Асинхронная поддержка - Серверная валидация с debouncing
- Работает с Behaviors - Идеальная синхронизация
Валидация vs Behaviors
Наша форма теперь имеет обе:
| Функция | Behaviors | Валидация |
|---|---|---|
| Назначение | Автоматизировать взаимодействия | Обеспечить качество данных |
| Когда запускается | При изменении полей | При изменении полей + отправка |
| Примеры | - Показ/скрытие полей - Вычисление значений - Копирование данных | - Обязательные поля - Проверка формата - Бизнес-правила |
| Обратная связь пользователю | Визуальные изменения | Сообщения об ошибках |
Они работают вместе:
- Behaviors скрывают поля → Валидация пропускает их
- Behaviors вычисляют значения → Валидация проверяет их
- Behaviors включают/отключают → Валидация уважает состояние
Что дальше?
Форма теперь имеет продвинутую валидацию, но нам ещё нужно обработать поток данных и отправку. В следующих разделах мы покроем:
Поток данных (следующий раздел)
- Загрузка начальных данных формы
- Сохранение прогресса формы (автосохранение)
- Сброс состояния формы
- Клонирование и дублирование форм
Отправка (следующий раздел)
- Обработка отправки формы
- Коммуникация с сервером
- Обработка успеха и ошибки
- Оптимистичные обновления
- Логика повторных попыток
Валидация которую мы создали будет бесшовно интегрирована с этими функциями!