Ошибки при типизации

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

Типичная ошибка возникает при ручном описании интерфейсов:

interface User {
  name: string;
  age: number;
}

и несоответствующей схеме:

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

На уровне TypeScript всё выглядит корректно, но в рантайме значение age ожидается как строка, что противоречит интерфейсу. Подобные рассинхронизации приводят к скрытым ошибкам, которые проявляются только во время выполнения.


Неправильное использование InferType

Механизм вывода типов через yup.InferType предназначен для устранения дублирования типов и схемы. Однако его некорректное применение часто приводит к обратному эффекту.

const schema = yup.object({
  id: yup.number().required(),
  email: yup.string().email().required(),
});

type User = yup.InferType<typeof schema>;

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

Дополнительная ошибка — попытка расширить тип вручную:

type User = yup.InferType<typeof schema> & {
  isAdmin: boolean;
};

При этом поле isAdmin отсутствует в схеме, что приводит к ситуации, когда TypeScript допускает значение, которое Yup никогда не пропустит.


Игнорирование nullable, defined и optional

Семантика значений null, undefined и отсутствующих полей часто интерпретируется неправильно. Yup различает эти состояния, но разработчики нередко смешивают их в типах.

const schema = yup.object({
  middleName: yup.string().nullable(),
});

В этом случае значение может быть либо строкой, либо null. Однако при использовании:

type User = {
  middleName?: string;
};

возникает расхождение: TypeScript допускает undefined, тогда как Yup ожидает либо строку, либо null, но не undefined.

Особенно критично это проявляется при работе с формами, где пустые поля часто интерпретируются как undefined.


Ошибки при работе с default

Метод default в Yup влияет только на значение после валидации, но не изменяет тип напрямую.

const schema = yup.object({
  role: yup.string().default("user"),
});

Тип, полученный через InferType, не всегда учитывает наличие значения по умолчанию как гарантированного. Это приводит к следующей проблеме: разработчик ожидает, что поле всегда определено, но TypeScript продолжает считать его потенциально неопределённым.

Дополнительная ошибка возникает при комбинировании default и optional, где логика становится неоднозначной:

role: yup.string().default("user").notRequired();

В рантайме значение существует, но типовая система допускает отсутствие поля.


Конфликты между required и strict

Флаг required() в Yup влияет на валидацию, но не всегда синхронизируется с TypeScript-типами. Ещё более сложная ситуация возникает при использовании strict().

const schema = yup.string().strict().required();

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

Распространённая ошибка — ожидание автоматического сужения типов при включении строгого режима, чего не происходит.


Проблемы с вложенными объектами

При глубокой вложенности схем типизация становится особенно уязвимой.

const schema = yup.object({
  user: yup.object({
    profile: yup.object({
      age: yup.number().required(),
    }),
  }),
});

Даже при использовании InferType возможны ситуации, когда промежуточные поля воспринимаются как необязательные, особенно если где-либо в цепочке применялись nullable() или default().

Ошибка проявляется в виде:

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

Ошибки с массивами и as const поведением

Схемы массивов часто становятся источником несоответствий:

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

TypeScript может вывести тип как (object | undefined)[], если не задана строгая структура элементов. Это приводит к необходимости ручного уточнения типов, что ломает идею автоматического вывода.

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


Потеря типов при использовании mixed

mixed является наиболее универсальным, но и наиболее опасным типом.

const schema = yup.mixed();

При использовании этого типа TypeScript теряет информацию о структуре данных полностью. Даже при добавлении проверок через test, статическая типизация остаётся неосведомлённой о реальных ограничениях.

Частая ошибка — использование mixed как временного решения с последующим забыванием уточнить тип.


Неправильная композиция схем

Комбинирование схем через concat и shape часто приводит к неожиданным результатам типизации.

const base = yup.object({
  id: yup.number().required(),
});

const extended = base.shape({
  name: yup.string().required(),
});

TypeScript может не отразить изменения корректно, особенно если базовая схема была уже выведена через InferType. Это создаёт эффект «замороженного типа», который не учитывает последующие расширения.


Потеря синхронизации при разделении схем и типов

Одной из системных проблем является разделение:

  • схем Yup в одном модуле
  • типов TypeScript в другом

Даже при использовании InferType возможны случаи, когда типы кэшируются или переиспользуются без пересборки. Это приводит к ситуации, когда изменения валидации не отражаются в типах и наоборот.


Скрытые ошибки при преобразовании данных

Yup позволяет трансформировать значения через transform, что создаёт дополнительный слой несоответствия.

yup.number().transform((value, original) =>
  original === "" ? null : value
);

Тип остаётся number, хотя фактически значение может стать null. Подобные трансформации часто приводят к рассинхронизации между тем, что допускает схема, и тем, что ожидает TypeScript.


Итоговые типовые ловушки

Наиболее устойчивые проблемы типизации в Yup формируются вокруг следующих механизмов:

  • автоматический вывод типов без контроля схемы
  • несоответствие nullable, optional, required
  • игнорирование влияния default на конечную форму данных
  • потеря точности при вложенных объектах и массивах
  • использование mixed как универсального типа
  • отсутствие синхронизации между изменениями схем и типами

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