Одной из ключевых особенностей работы с Yup в связке с TypeScript
является возможность автоматически извлекать тип данных из схемы
валидации. Это решается через утилитный тип InferType,
который позволяет синхронизировать runtime-валидацию и статическую
типизацию без дублирования описаний.
Основная идея заключается в том, что схема Yup становится единственным источником истины, а TypeScript тип выводится непосредственно из неё.
Yup.InferTypeТип InferType применяется к схеме следующим образом:
import * as Yup from 'yup';
const userSchema = Yup.object({
id: Yup.number().required(),
name: Yup.string().required(),
email: Yup.string().email().required(),
});
Из этой схемы можно вывести тип:
type User = Yup.InferType<typeof userSchema>;
В результате User будет эквивалентен:
type User = {
id: number;
name: string;
email: string;
};
Таким образом, любое изменение схемы автоматически отражается в типе данных.
InferType корректно отображает все базовые типы Yup:
Yup.string() → stringYup.number() → numberYup.boolean() → booleanYup.date() → DateПример:
const schema = Yup.object({
title: Yup.string(),
views: Yup.number(),
published: Yup.boolean(),
createdAt: Yup.date(),
});
type Post = Yup.InferType<typeof schema>;
Результат:
type Post = {
title?: string;
views?: number;
published?: boolean;
createdAt?: Date;
};
Yup по умолчанию делает поля необязательными, если не указан
.required().
const schema = Yup.object({
title: Yup.string().required(),
description: Yup.string(),
});
Тип будет:
type Result = {
title: string;
description?: string | undefined;
};
Важно, что InferType сохраняет информацию о возможности
undefined, что позволяет корректно работать с проверками на
уровне TypeScript.
InferType полноценно поддерживает вложенные
структуры:
const schema = Yup.object({
user: Yup.object({
id: Yup.number().required(),
profile: Yup.object({
firstName: Yup.string(),
lastName: Yup.string(),
}),
}),
});
Тип:
type Result = {
user?: {
id: number;
profile?: {
firstName?: string;
lastName?: string;
};
};
};
Вложенность сохраняется рекурсивно, включая все уровни схемы.
InferTypeДля массивов используется Yup.array():
const schema = Yup.object({
tags: Yup.array().of(Yup.string().required()),
});
Тип:
type Result = {
tags?: string[];
};
Если внутри массива сложный объект:
const schema = Yup.object({
comments: Yup.array().of(
Yup.object({
id: Yup.number(),
text: Yup.string(),
})
),
});
Результат:
type Result = {
comments?: {
id?: number;
text?: string;
}[];
};
oneOf, mixedПри использовании mixed или ограничений через
oneOf типизация становится более узкой:
const schema = Yup.object({
status: Yup.mixed<'draft' | 'published' | 'archived'>().oneOf([
'draft',
'published',
'archived',
]),
});
Тип:
type Result = {
status?: 'draft' | 'published' | 'archived';
};
Это позволяет использовать Yup как источник строго типизированных union-значений.
nullable и влияние на
типПри использовании nullable() добавляется
null в тип:
const schema = Yup.object({
name: Yup.string().nullable(),
});
Тип:
type Result = {
name?: string | null;
};
Это важно учитывать при работе с API, где отсутствие значения и
null различаются.
default() и влияние
на InferTypeЕсли задано значение по умолчанию:
const schema = Yup.object({
role: Yup.string().default('user'),
});
Тип остаётся:
type Result = {
role?: string;
};
Важно: InferType не подставляет runtime-дефолты в тип
как обязательные значения, так как они применяются только при
валидации.
InferTypeНесмотря на мощность, существуют ограничения:
Некоторые трансформации не отражаются в типе:
Yup.string().transform((value) => value.trim())
TypeScript не учитывает .transform().
when)При использовании .when() тип становится объединённым и
может терять точность:
const schema = Yup.object({
isAdmin: Yup.boolean(),
role: Yup.string().when('isAdmin', {
is: true,
then: (s) => s.required(),
otherwise: (s) => s.optional(),
}),
});
Типизация не всегда корректно отражает условность обязательности.
При использовании Yup.mixed() с кастомной логикой
итоговый тип часто требует явного уточнения через дженерики.
Можно явно задавать тип, если требуется усилить контроль:
const schema: Yup.ObjectSchema<{ id: number }> = Yup.object({
id: Yup.number().required(),
});
type Result = Yup.InferType<typeof schema>;
В этом случае тип будет строго согласован с дженериком схемы.
При объединении схем через concat тип также
объединяется:
const base = Yup.object({
id: Yup.number(),
});
const extended = base.concat(
Yup.object({
name: Yup.string(),
})
);
type Result = Yup.InferType<typeof extended>;
Результат:
type Result = {
id?: number;
name?: string;
};
InferType в архитектуреИспользование InferType устраняет необходимость ручного
поддержания типов DTO и схем валидации отдельно. Это снижает риск
рассинхронизации между:
Схема становится единым контрактом данных, а TypeScript — её статическим отражением.