Современные формы часто существуют внутри больших кодовых баз, где уже есть собственные механизмы валидации, обработки ошибок и управления состоянием. При внедрении схемной валидации через YupResolver ключевой задачей становится не замена всей инфраструктуры, а корректное сосуществование с уже работающими компонентами.
Наследуемые системы обычно обладают следующими характеристиками:
В таких условиях внедрение YupResolver требует промежуточного слоя совместимости.
YupResolver в экосистеме React Hook Form выполняет функцию транслятора схемы Yup в формат, понятный системе управления формами. Однако в контексте legacy-кода он становится дополнительным адаптером между старой и новой моделью валидации.
Ключевая идея интеграции:
Yup schema → YupResolver → RHF → legacy handlers
При этом legacy-слой не обязан знать о существовании Yup или resolver-логики.
Один из наиболее безопасных способов внедрения заключается в создании адаптера вокруг существующих функций проверки.
function legacyValidate(values) {
const errors = {};
if (!values.email || values.email.indexOf('@') === -1) {
errors.email = "Некорректный email";
}
if (values.password && values.password.length < 6) {
errors.password = "Слишком короткий пароль";
}
return errors;
}
Такая функция возвращает объект ошибок, но не соответствует формату Yup.
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(6)
});
const resolver = async (values, context, options) => {
const yupResult = await yupResolver(schema)(values, context, options);
const legacyErrors = legacyValidate(values);
return {
values: yupResult.values || {},
errors: {
...yupResult.errors,
...legacyErrors
}
};
};
Такой подход позволяет сохранить существующую бизнес-логику без её переписывания.
В больших системах полный переход на Yup невозможен в одном релизе. Используется стратегия постепенного замещения.
На первом этапе обе системы работают одновременно:
const resolver = async (values, context, options) => {
const yupResult = await yupResolver(schema)(values, context, options);
const legacyErrors = legacyValidate(values);
const mergedErrors = {
...yupResult.errors,
...legacyErrors
};
return {
values: yupResult.values,
errors: mergedErrors
};
};
Главная цель — не допустить деградации функционала.
Legacy-валидация постепенно отключается на отдельных полях.
const legacyValidate = (values) => {
const errors = {};
// email уже мигрирован в Yup
if (!values.username) {
errors.username = "Обязательное поле";
}
return errors;
};
После завершения миграции resolver становится чистым:
const resolver = yupResolver(schema);
Многие старые формы используют событийную модель:
form.onSub mit = function(values, callback) {
const errors = legacyValidate(values);
if (Object.keys(errors).length > 0) {
callback(errors);
return;
}
api.save(values, callback);
};
const bridgeSubmit = async (values) => {
const result = await resolver(values);
if (Object.keys(result.errors).length > 0) {
return result.errors;
}
return new Promise((resolve, reject) => {
api.save(result.values, (err, response) => {
if (err) reject(err);
else resolve(response);
});
});
};
Таким образом, YupResolver становится первым этапом пайплайна.
Legacy-системы часто используют нестандартизированные структуры данных:
Yup ожидает строгую структуру, тогда как legacy-код допускает вариативность.
function normalize(values) {
return {
...values,
age: Number(values.age || 0),
profile: values.profile || {},
email: (values.email || '').trim()
};
}
const resolver = async (values, context, options) => {
const normalized = normalize(values);
const result = await yupResolver(schema)(normalized, context, options);
return result;
};
В старых системах ошибки часто хранятся в DOM или отдельных объектах состояния:
formState.errors.emailMessage = "Ошибка";
function mapErrorsToLegacyFormat(errors) {
const legacy = {};
Object.keys(errors).forEach(key => {
legacy[key + "Message"] = errors[key]?.message;
});
return legacy;
}
const result = await resolver(values);
const legacyErrors = mapErrorsToLegacyFormat(result.errors);
legacyForm.setErrors(legacyErrors);
Legacy-код часто содержит доменные проверки, не выражаемые через Yup.
Пример:
function checkBusinessRules(values) {
if (values.country === "restricted" && values.amount > 1000) {
return {
amount: "Превышен лимит для выбранной страны"
};
}
return {};
}
const resolver = async (values) => {
const yupResult = await yupResolver(schema)(values);
const businessErrors = checkBusinessRules(values);
return {
values: yupResult.values,
errors: {
...yupResult.errors,
...businessErrors
}
};
};
В старых архитектурах формы часто управляются через Redux:
const validateForm = (values) => async (dispatch) => {
const result = await resolver(values);
dispatch({
type: "FORM_ERRORS_UPDATED",
payload: result.errors
});
if (Object.keys(result.errors).length === 0) {
dispatch({ type: "FORM_VALID" });
}
};
Некоторые системы используют серверную проверку в реальном времени:
function checkEmailExists(email, callback) {
api.check(email, callback);
}
const schema = yup.object({
email: yup.string().email().test(
"exists",
"Email уже используется",
async (value) => {
const res = await api.check(value);
return !res.exists;
}
)
});
В legacy-контексте это позволяет заменить callback-модель на promise-based подход без изменения интерфейса формы.
В сложных системах форма может состоять из нескольких независимых модулей:
const billingResolver = yupResolver(billingSchema);
const profileResolver = yupResolver(profileSchema);
const combinedResolver = async (values) => {
const billing = await billingResolver(values.billing);
const profile = await profileResolver(values.profile);
return {
values: {
billing: billing.values,
profile: profile.values
},
errors: {
billing: billing.errors,
profile: profile.errors
}
};
};
Legacy-системы часто используют разные уровни severity:
Yup работает только с ошибками.
function extendErrors(errors) {
const extended = {};
Object.keys(errors).forEach(key => {
extended[key] = {
message: errors[key].message,
severity: "error"
};
});
return extended;
}
В процессе миграции важно сохранять обе модели:
Такой подход позволяет:
Критические места внедрения YupResolver в legacy-систему:
Каждая точка должна быть изолирована через адаптер, чтобы избежать каскадных изменений в кодовой базе.
После подключения YupResolver в legacy-окружение часто возникают различия в:
Стабилизация достигается через фиксированные правила мержа: