Валидация, основанная на внешних источниках данных, используется тогда, когда локальной проверки схемы недостаточно и требуется обращение к серверу, API или другой системе хранения. Такой подход особенно важен при проверке уникальности значений, актуальности справочных данных, прав доступа и бизнес-правил, которые невозможно выразить статическими ограничениями.
В связке с резолверами схем в формах, таких как
YupResolver из экосистемы интеграции Yup и react-hook-form,
появляется возможность комбинировать декларативные правила и асинхронные
проверки, сохраняя единый подход к описанию валидации.
Внешняя валидация отличается тем, что результат проверки зависит от состояния системы, находящейся вне текущего процесса выполнения. Это может быть:
Такие операции обладают двумя ключевыми характеристиками:
Асинхронность Ответ приходит с задержкой, что
требует поддержки Promise-механики.
Нестабильность результата Результат может меняться во времени даже для одинакового входа.
Yup поддерживает асинхронные проверки через метод test,
который может возвращать Promise. Это позволяет встроить
внешний запрос прямо в схему.
Базовая структура выглядит следующим образом:
import * as Yup from "yup";
const schema = Yup.object({
email: Yup.string()
.email("Некорректный формат email")
.required("Обязательное поле")
.test("check-email", "Email уже используется", async (value) => {
if (!value) return true;
const response = await fetch(`/api/check-email?email=${value}`);
const data = await response.json();
return data.available === true;
})
});
Асинхронный test возвращает true или
false, либо выбрасывает исключение, которое трактуется как
ошибка валидации.
Роль резолвера заключается в преобразовании схемы Yup в формат,
который понимает форма. В контексте react-hook-form это происходит через
@hookform/resolvers/yup.
YupResolver выполняет следующие задачи:
react-hook-form;{ values, errors }.Пример подключения:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
const schema = Yup.object({
username: Yup.string()
.required()
.test("check-username", "Имя занято", async (value) => {
const res = await fetch(`/api/username/${value}`);
const data = await res.json();
return data.available;
})
});
function Form() {
const { register, handleSubmit, formState } = useForm({
resolver: yupResolver(schema)
});
return null;
}
При использовании внешней валидации возникают особенности, которые напрямую влияют на поведение формы.
Все test-проверки внутри Yup выполняются
последовательно. Это означает, что:
required, min,
matches) не пройдена, асинхронный запрос не
выполняется;Это позволяет экономить сетевые ресурсы.
При вводе пользователем данных может происходить множество быстрых изменений значения поля. Это приводит к ситуации:
user1;user12;Такое поведение называется гонкой запросов и требует дополнительного контроля.
Решения:
AbortController;let controller;
Yup.string().test("check", "Ошибка", async (value) => {
if (controller) controller.abort();
controller = new AbortController();
const res = await fetch(`/api/check?value=${value}`, {
signal: controller.signal
});
const data = await res.json();
return data.ok;
});
При усложнении логики не рекомендуется перегружать Yup-схему прямыми HTTP-вызовами. Более устойчивый подход заключается в вынесении логики в сервисный слой.
async function checkEmailAvailability(email) {
const res = await fetch(`/api/email/check`, {
method: "POST",
body: JSON.stringify({ email })
});
const data = await res.json();
return data.available;
}
И использование в схеме:
Yup.string().test("email-check", "Email занят", async (value) => {
return await checkEmailAvailability(value);
});
Такой подход упрощает тестирование и повышает переиспользуемость.
При частых проверках одних и тех же значений имеет смысл использовать кэширование. Это снижает нагрузку на API и ускоряет реакцию интерфейса.
const cache = new Map();
async function cachedCheckUsername(username) {
if (cache.has(username)) {
return cache.get(username);
}
const res = await fetch(`/api/username/${username}`);
const data = await res.json();
cache.set(username, data.available);
return data.available;
}
Внешние источники могут быть недоступны, возвращать ошибки или некорректные данные. Валидация должна учитывать такие сценарии.
Основные стратегии:
Fallback-разрешение При ошибке сервиса считать поле валидным:
.test("check", async (value) => {
try {
return await apiCheck(value);
} catch {
return true;
}
});
Жёсткая блокировка При ошибке считать поле невалидным:
.test("check", async (value) => {
try {
return await apiCheck(value);
} catch {
return false;
}
});
Выбор стратегии определяется критичностью данных.
Внешняя валидация должна быть минимально навязчивой для API. Основные техники оптимизации:
blur, а не
change;Пример условного запуска:
.test("check", async (value) => {
if (value.length < 3) return true;
return await apiCheck(value);
});
react-hook-form выполняет валидацию через резолвер при определённых триггерах:
onChangeonBluronSubmitПри использовании YupResolver важно учитывать, что:
isValid становится нестабильным при частых
изменениях;formState.errors обновляется после завершения всех
промисов.Комбинация локальной и внешней валидации часто реализуется через двухэтапный подход:
const schema = Yup.object({
code: Yup.string()
.required()
.min(6)
.test("verify-code", async (value) => {
if (!value) return false;
return await verifyPromoCode(value);
})
});
Асинхронная валидация влияет на поведение интерфейса:
Хотя сама схема Yup не управляет UI, YupResolver
передаёт промежуточные состояния в форму, которые могут использоваться
для отображения статуса проверки.
Внешняя валидация не должна рассматриваться как единственный источник истины. Даже при наличии Yup-схемы с асинхронными проверками сервер обязан повторно валидировать данные, поскольку клиентская логика может быть:
YupResolver в этом контексте выступает как инструмент предварительной проверки, оптимизирующий пользовательский ввод, но не заменяющий серверную валидацию.