Методы трансформации данных

В Yup трансформация данных выполняется на уровне схемы в момент валидации и приведения значений к ожидаемым типам. Это позволяет не только проверять данные, но и нормализовать их перед тем, как они попадут в бизнес-логику приложения. В связке с резолверами, такими как @hookform/resolvers/yup, трансформация становится частью общего пайплайна обработки формы: сырые значения из UI сначала преобразуются схемой, затем проходят проверку, и только после этого возвращаются в форму или передаются дальше.

Метод transform() является ключевым инструментом изменения входных данных внутри Yup-схемы. Он применяется ко всем основным типам: строкам, числам, булевым значениям и даже объектам.

Сигнатура метода:

schema.transform((value, originalValue) => {
  return transformedValue;
});
  • value — текущее значение после предыдущих преобразований
  • originalValue — исходное значение, полученное из формы или внешнего источника

Приведение строк к числам

Одна из наиболее частых задач — преобразование строкового ввода в числовой формат:

import * as Yup from "yup";

const schema = Yup.object({
  age: Yup.number()
    .transform((value, originalValue) => {
      return originalValue === "" ? null : Number(originalValue);
    })
    .nullable()
});

В данном случае пустая строка не превращается в 0, что предотвращает логические ошибки при обработке форм.

Очистка строковых значений

transform() часто используется для нормализации строк:

const schema = Yup.object({
  username: Yup.string()
    .transform((value) => {
      return value ? value.trim().toLowerCase() : value;
    })
});

Такая трансформация устраняет лишние пробелы и приводит значения к единому регистру.

Типовая коэрция и внутренние преобразования Yup

Yup обладает встроенной системой приведения типов (type casting), которая активируется до этапа валидации. Однако её поведение можно переопределять через transform().

Приведение строк к boolean

const schema = Yup.object({
  isActive: Yup.boolean().transform((value, originalValue) => {
    if (originalValue === "true") return true;
    if (originalValue === "false") return false;
    return value;
  })
});

Без явной трансформации строковые значения могли бы интерпретироваться некорректно или вызывать ошибки валидации.

Контроль пустых значений

Yup часто интерпретирует пустые строки как валидные значения, что требует ручной обработки:

const schema = Yup.object({
  description: Yup.string().transform((value, originalValue) => {
    return originalValue === "" ? undefined : value;
  })
});

Такое преобразование позволяет корректно использовать required() без ложных срабатываний.

Метод cast: явное приведение схемы

Метод cast() используется для преобразования произвольного значения в тип, определённый схемой, без запуска полной валидации.

const schema = Yup.number();

schema.cast("42"); // 42

В отличие от validate(), метод cast():

  • не выбрасывает ошибки
  • не применяет строгие проверки
  • только приводит типы

Каст сложных структур

const schema = Yup.object({
  id: Yup.number(),
  tags: Yup.array().of(Yup.string())
});

const result = schema.cast({
  id: "10",
  tags: [1, 2, 3]
});

Результат:

{
  id: 10,
  tags: ["1", "2", "3"]
}

Это особенно важно при работе с API, где типы данных часто не соответствуют ожидаемым.

Default значения и их влияние на трансформацию

Метод default() задаёт значение, которое применяется до валидации и трансформации.

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

При отсутствии значения поле автоматически получит "user".

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

Порядок обработки:

  1. Применяется default()
  2. Выполняется transform()
  3. Проводится валидация
const schema = Yup.object({
  code: Yup.string()
    .default("  abc  ")
    .transform((value) => value.trim())
});

Результат всегда будет "abc".

Нормализация массивов и объектов

Трансформация особенно важна при работе со сложными структурами данных.

Очистка массива

const schema = Yup.object({
  tags: Yup.array()
    .transform((value) => {
      return Array.isArray(value)
        ? value.filter(Boolean)
        : [];
    })
});

Здесь удаляются falsy-значения (null, undefined, "", 0).

Преобразование вложенных объектов

const schema = Yup.object({
  user: Yup.object({
    name: Yup.string().transform((v) => v?.trim()),
    age: Yup.number().transform((v, ov) => Number(ov))
  })
});

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

Особенности трансформации в контексте YupResolver

В связке с @hookform/resolvers/yup трансформации применяются до возврата результата в React Hook Form. Это означает, что:

  • значения формы могут изменяться ещё до onSubmit
  • errors формируются уже на основе трансформированных данных
  • UI может получать уже нормализованные значения

Пример использования:

import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

const schema = Yup.object({
  price: Yup.number()
    .transform((v, ov) => Number(ov))
});

const { register, handleSubmit } = useForm({
  resolver: yupResolver(schema)
});

В этом случае строковое значение "100" автоматически преобразуется в 100 до этапа проверки.

Порядок выполнения трансформаций

Внутренний пайплайн Yup можно описать следующим образом:

  1. Получение исходного значения
  2. Применение default()
  3. Выполнение цепочки transform()
  4. Приведение типов (cast)
  5. Валидация правил (required, min, max, matches и др.)

Понимание этого порядка критично для предсказуемого поведения схем.

Цепочки transform и их влияние

Несколько transform() могут применяться последовательно:

const schema = Yup.string()
  .transform((v) => v.trim())
  .transform((v) => v.toLowerCase())
  .transform((v) => v.replace(/\s+/g, "-"));

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

Ограничения и побочные эффекты трансформаций

Трансформация не должна использоваться для бизнес-логики. Её задача ограничена:

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

Сложные вычисления внутри transform() могут привести к:

  • непредсказуемым результатам валидации
  • расхождению между UI и моделью данных
  • затруднённой отладке схем

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