Автоматический вывод типов

Автоматический вывод типов в Yup позволяет синхронизировать схемы валидации с TypeScript-типами без ручного дублирования интерфейсов. Это снижает риск расхождений между описанием данных и их реальной структурой, а также упрощает поддержку кода в проектах, где валидация и типизация тесно связаны.

В основе автоматического вывода лежит утилитный тип yup.InferType, который извлекает TypeScript-тип из схемы.

import * as yup from "yup";

const schema = yup.object({
  name: yup.string().required(),
  age: yup.number().min(0).required(),
});

type User = yup.InferType<typeof schema>;

В результате User автоматически становится:

type User = {
  name: string;
  age: number;
};

Ключевая особенность заключается в том, что тип формируется не вручную, а на основе структуры схемы. Это особенно важно в крупных проектах, где количество полей может исчисляться десятками или сотнями.

Вывод типов для объектов

Объектные схемы являются основным источником сложных структур. Yup рекурсивно анализирует каждое поле и собирает итоговый тип.

const schema = yup.object({
  id: yup.string().required(),
  profile: yup.object({
    firstName: yup.string(),
    lastName: yup.string(),
  }),
});

type Result = yup.InferType<typeof schema>;

Результирующий тип:

type Result = {
  id: string;
  profile?: {
    firstName?: string;
    lastName?: string;
  };
};

Особенность заключается в том, что optional() и required() напрямую влияют на итоговую обязательность полей в TypeScript.

Массивы и вложенные структуры

При использовании массивов вывод типов распространяется на каждый элемент коллекции.

const schema = yup.object({
  tags: yup.array().of(yup.string().required()),
});

Тип будет интерпретирован как:

type Result = {
  tags?: string[];
};

Если массив содержит объекты, типизация становится вложенной:

const schema = yup.object({
  items: yup.array().of(
    yup.object({
      id: yup.number(),
      title: yup.string(),
    })
  ),
});

Результат:

type Result = {
  items?: {
    id?: number;
    title?: string;
  }[];
};

Влияние преобразований (transform)

Yup поддерживает преобразование значений, что может менять итоговый тип данных. Однако TypeScript не всегда способен автоматически отследить такие изменения.

const schema = yup.object({
  count: yup.number().transform((value, originalValue) => {
    return typeof originalValue === "string"
      ? parseInt(originalValue, 10)
      : value;
  }),
});

Несмотря на трансформацию, тип остаётся number, так как система вывода ориентируется на базовый тип схемы, а не на runtime-логику.

Nullable и optional значения

Различие между nullable() и optional() отражается в типах по-разному.

const schema = yup.object({
  title: yup.string().nullable(),
  description: yup.string().optional(),
});

Результат:

type Result = {
  title: string | null;
  description?: string;
};

Таким образом:

  • nullable() добавляет null в тип
  • optional() делает поле необязательным

Mixed и неопределённые типы

Тип mixed() используется для значений, которые не имеют строгого ограничения. В этом случае вывод становится максимально широким.

const schema = yup.object({
  value: yup.mixed(),
});

Тип:

type Result = {
  value?: any;
};

При отсутствии уточнений TypeScript теряет возможность строгой проверки.

Enum и литеральные типы

При использовании oneOf Yup может выводить объединения литеральных типов.

const schema = yup.object({
  role: yup.string().oneOf(["admin", "user", "guest"]),
});

Тип:

type Result = {
  role?: "admin" | "user" | "guest";
};

Это позволяет ограничивать допустимые значения без дополнительных интерфейсов.

Условные схемы и ограничения вывода

При использовании when и условной логики вывод типов становится менее точным, так как структура может зависеть от runtime-значений.

const schema = yup.object({
  type: yup.string(),
  value: yup.mixed().when("type", {
    is: "number",
    then: yup.number(),
    otherwise: yup.string(),
  }),
});

В подобных случаях итоговый тип может расширяться до объединения:

type Result = {
  type?: string;
  value?: string | number;
};

TypeScript не способен полностью восстановить условную логику схемы, поэтому результат становится обобщённым.

Работа с as const и статической типизацией

Хотя Yup не требует использования as const, он может усиливать точность вывода при сочетании с литеральными значениями.

const roles = ["admin", "user"] as const;

const schema = yup.object({
  role: yup.mixed<typeof roles[number]>().oneOf(roles),
});

В этом случае тип остаётся строго ограниченным значениями массива.

Ограничения автоматического вывода

Несмотря на высокую степень интеграции с TypeScript, механизм вывода имеет ограничения:

  • сложные условные схемы теряют точность
  • динамические схемы (yup.lazy) не всегда корректно типизируются
  • кастомные трансформации не отражаются в типах
  • mixed() приводит к ослаблению строгой типизации
const schema = yup.lazy((value) => {
  if (typeof value === "string") {
    return yup.string();
  }
  return yup.number();
});

В таких случаях итоговый тип часто становится объединением или unknown, в зависимости от конфигурации.

Практическая роль InferType в архитектуре

Использование yup.InferType позволяет строить архитектуру, в которой схема становится единым источником истины для структуры данных. Это устраняет необходимость поддерживать параллельно:

  • интерфейсы TypeScript
  • схемы валидации
  • DTO-описания

Схема выступает центральным элементом, а типы автоматически выводятся из неё, снижая вероятность несоответствий между слоями приложения.