Nullable и optional поля

Nullable и optional поля в контексте схем валидации на базе Yup имеют принципиальное различие, которое напрямую влияет на поведение форм, сериализацию данных и результат работы резолвера в связке с YupResolver и React Hook Form.

Внутренне обработка значений разделяется на два независимых состояния отсутствия данных:

  • undefined — поле не передано вообще
  • null — поле передано, но явно пустое

Эта разница становится критичной при построении схем, поскольку:

  • undefined обычно трактуется как «не заполнено пользователем»
  • null трактуется как «значение существует, но отсутствует по смыслу»

Валидационные схемы по умолчанию ориентированы на undefined как признак отсутствия значения, тогда как null требует явного разрешения через nullable().

Опциональные поля и семантика .optional()

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

В Yup опциональность выражается неявно: поле считается опциональным, если отсутствует требование required().

Типичное поведение:

  • поле отсутствует (undefined) — валидно
  • поле присутствует — проходит дальнейшую проверку по типу и ограничениям

Пример схемы:

import * as yup from "yup";

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

В этом случае username:

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

Важно, что отсутствие required() не означает допустимость null.

Nullable поля и .nullable()

Метод .nullable() изменяет допустимые значения поля, разрешая null как валидное значение.

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

Поведение:

  • undefined — допустимо (если нет required)
  • null — допустимо
  • строка — допустимо
  • любые другие типы — ошибка

Ключевая особенность заключается в том, что null перестаёт приводиться к ошибке типа.

Влияние на трансформации

При использовании трансформаций null часто требует отдельной обработки:

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

Такой подход используется для унификации пустых строк и null.

Различие .nullable() и отсутствия required

Разделение логики:

  • опциональность (undefined) — управляет наличием поля
  • nullable (null) — управляет допустимым значением

Сравнение поведения:

Схема undefined null значение
string() допустимо ошибка строка
string().nullable() допустимо допустимо строка
string().required() ошибка ошибка строка
string().nullable().required() ошибка ошибка строка

Последний случай демонстрирует, что required() сильнее nullable() и запрещает отсутствие значения в любом виде.

Взаимодействие с YupResolver

В связке с форм-менеджером React Hook Form резолвер выполняет преобразование входных данных перед валидацией.

Ключевые моменты:

  • undefined часто приходит из незаполненных контролов
  • null может приходить из кастомных компонентов (select, datepicker)
  • резолвер не нормализует значения автоматически

Следовательно, схема должна явно описывать допустимые состояния.

Пример поведения:

const schema = yup.object({
  birthday: yup.date().nullable(),
});

Если компонент возвращает null при очистке поля, без .nullable() будет получена ошибка несоответствия типа.

Значения по умолчанию и влияние резолвера

При использовании React Hook Form значения по умолчанию задаются через defaultValues.

Поведение:

  • отсутствующее поле в defaultValuesundefined
  • явно заданное null → передаётся как значение

Это влияет на поведение Yup:

  • undefined игнорируется для optional полей
  • null требует разрешения через .nullable()

Особенно критично для форм с динамическими полями.

Комбинация .nullable() и required()

Комбинация выглядит противоречивой, но используется для тонкой настройки:

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

Логика:

  • required() запрещает undefined
  • nullable() разрешает null на уровне типа
  • итоговое поведение зависит от порядка и внутренних проверок Yup

Фактический результат:

  • undefined → ошибка (required)
  • null → часто трактуется как пустое значение и приводит к ошибке required
  • строка → допустимо

Использование такой комбинации обычно связано с миграцией схем или обработкой API, где null приходит как временное состояние.

Типовые ошибки при работе с nullable и optional

Ошибка 1: ожидание, что optional допускает null

yup.string()

Предположение: допустимы null и undefined

Фактическое поведение:

  • undefined допустимо
  • null вызывает ошибку

Ошибка 2: отсутствие нормализации пустых значений

Формы часто передают:

  • "" вместо null
  • null вместо undefined

Без трансформации схема становится непредсказуемой.

Ошибка 3: несовместимость с UI-компонентами

DatePicker и Sel ect часто возвращают null при очистке. Без .nullable() возникает ошибка несоответствия типов даже при корректной логике формы.

Практическая модель поведения значений

При проектировании схемы обычно используется следующая логика:

  • undefined — поле не участвовало в заполнении
  • null — пользователь явно очистил значение
  • значение типа T — валидное заполнение

На уровне Yup это выражается комбинацией:

  • .optional() (по умолчанию)
  • .nullable() (при необходимости поддержки очистки)
  • .transform() (для унификации входных данных)

Пример комплексной схемы

import * as yup fr om "yup";

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

  phone: yup.string().nullable(),

  age: yup.number().nullable().transform((value, originalValue) =>
    originalValue === "" ? null : value
  ),

  comment: yup.string(),
});

В данной конфигурации:

  • name, email — строго обязательные значения
  • phone — допускает отсутствие и явный null
  • age — нормализует пустую строку в null
  • comment — полностью опциональное поле без допуска null