Required и optional

Валидационная модель Yup строится на декларативном описании формы данных, где каждое поле явно классифицируется как обязательное или необязательное. При интеграции с YupResolver (чаще всего в связке с React Hook Form через @hookform/resolvers/yup) эта классификация напрямую влияет на формирование ошибок, типизацию и поведение формы при сабмите.

Семантика обязательных полей в Yup

Обязательность в Yup задаётся через метод required(), который меняет поведение схемы и заставляет валидатор считать отсутствие значения ошибкой.

import * as yup from "yup";

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

Поведение required() в контексте Yup:

  • undefined трактуется как отсутствие значения
  • пустая строка "" также считается невалидной для строковых полей
  • null не считается валидным значением, если явно не разрешён

Внутренне Yup формирует специальное условие, которое проверяется после базовой валидации типа. Это означает, что порядок вызовов методов имеет значение: сначала выполняется типовая проверка (string, number, date), затем ограничения (email, min, max), и только потом — required().

Пример с кастомным сообщением:

yup.string().required("Поле обязательно для заполнения");

Необязательные поля и их поведение

Необязательные поля в Yup задаются не отсутствием required(), а явным указанием notRequired() либо отсутствием обязательных ограничений в цепочке валидации.

const schema = yup.object({
  nickname: yup.string().notRequired(),
});

Особенность Yup заключается в том, что поле считается необязательным по умолчанию, если отсутствует required(). Однако поведение становится менее очевидным при добавлении других правил:

yup.string().min(3);

В этом случае поле остаётся необязательным, но при наличии значения оно должно соответствовать правилам.

Различие между notRequired, nullable и отсутствием значения

  • notRequired() — поле может отсутствовать (undefined допускается)
  • nullable() — поле может принимать null как валидное значение
  • отсутствие required() — поведение по умолчанию, эквивалентно необязательности, но без явной семантики

Комбинации:

yup.string().nullable().notRequired();

Такое определение допускает undefined, null и строку.

Пограничные значения: undefined, null, пустая строка

В контексте YupResolver критически важно различать три состояния отсутствия данных:

  • undefined — поле не задано в объекте формы
  • null — явно переданное пустое значение
  • "" — пустая строка, часто возникающая в HTML-инпутах

Для строковых схем:

  • required() блокирует ""
  • nullable() разрешает null, но не влияет на ""
  • transform() часто используется для нормализации пустых строк в undefined

Пример нормализации:

yup.string().transform((value, originalValue) =>
  originalValue === "" ? undefined : value
);

Поведение YupResolver при обработке required/optional

YupResolver преобразует Yup-схему в функцию валидации, которая возвращает объект ошибок в формате, совместимом с React Hook Form.

import { yupResolver } from "@hookform/resolvers/yup";

Внутренний процесс включает:

  1. Получение значений формы из React Hook Form
  2. Прогон через schema.validate(...)
  3. Сбор всех ошибок в структурированный объект
  4. Возврат результата в RHF

Обязательные поля влияют на формирование ValidationError, который затем маппится в formState.errors.

Если поле отсутствует:

  • при required() создаётся ошибка с ключом поля
  • при отсутствии required() поле игнорируется, если не нарушены другие правила

Влияние defaultValues на required/optional

В React Hook Form поведение required/optional тесно связано с defaultValues.

const form = useForm({
  resolver: yupResolver(schema),
  defaultValues: {
    email: "",
  },
});

Если defaultValues.email задан как пустая строка:

  • без required() поле остаётся валидным при отсутствии других ограничений
  • с required() пустая строка становится ошибкой

Если значение undefined:

  • Yup воспринимает это как отсутствие значения
  • активируется логика required()

Типизация required и optional полей

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

Пример:

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

Фактическая типизация может допускать:

  • email: string | undefined на уровне TS-инференса
  • различие определяется через InferType<typeof schema>

В связке с YupResolver типы формы могут быть шире, чем логическая обязательность.

Условная обязательность

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

yup.object({
  paymentMethod: yup.string(),
  cardNumber: yup.string().when("paymentMethod", {
    is: "card",
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),
});

Здесь обязательность вычисляется на этапе валидации, а YupResolver просто транслирует результат.

Обязательные поля в объектах и массивах

Для объектов:

yup.object({
  profile: yup.object({
    name: yup.string().required(),
  }).required(),
});

Важно различать:

  • object().required() — объект должен существовать
  • внутренние поля объекта — валидируются отдельно

Для массивов:

yup.array().of(
  yup.string().required()
).required();

Здесь:

  • массив как структура может быть обязательным
  • элементы массива могут иметь собственную обязательность

Ошибки и их структура при required/optional

YupResolver формирует объект ошибок с вложенной структурой:

{
  email: {
    type: "required",
    message: "Field is required"
  }
}

Для вложенных объектов:

{
  profile: {
    name: {
      type: "required",
      message: "Required"
    }
  }
}

Обязательность влияет на type, чаще всего:

  • required
  • typeError
  • nullable

Особенности поведения при смешанных схемах

Комбинации правил создают тонкие эффекты:

yup.string().nullable().required();

В таком случае:

  • null разрешён на уровне типа
  • required() всё равно может считать null ошибкой, если не настроена трансформация

Корректная интерпретация зависит от порядка и наличия transform().

Практика нормализации required/optional логики

Для унификации поведения часто применяется явная нормализация входных данных:

const schema = yup.object({
  comment: yup
    .string()
    .transform(v => (v === "" ? undefined : v))
    .notRequired(),
});

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