Превью и предобработка

Валидационный процесс в связке Yup + YupResolver в прикладных React-приложениях строится не только вокруг схемы, но и вокруг этапа подготовки данных. На практике именно предобработка определяет стабильность формы: одинаковые поля могут приходить в разных форматах, содержать «пустые» значения, строковые представления чисел или некорректные даты.

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

Типовой поток данных выглядит так:

raw input → нормализация → преобразование типов → схема Yup → результат валидации

Игнорирование первого этапа приводит к накоплению технического долга: схемы начинают разрастаться из-за попыток компенсировать грязные данные, а логика становится менее предсказуемой.


Роль YupResolver в цепочке обработки данных

YupResolver выступает связующим звеном между формой (например, react-hook-form) и схемой Yup. Его задача заключается не только в запуске валидации, но и в подготовке данных к корректной проверке.

В базовом виде resolver:

  • принимает значения формы
  • приводит их к ожидаемому формату (частично)
  • вызывает schema.validate
  • возвращает либо ошибки, либо валидированные данные

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


Этапы предобработки данных

Нормализация строковых значений

Наиболее частая проблема — строки с неочевидной семантикой:

  • " " (пробелы)
  • "" (пустая строка)
  • "null" или "undefined" как текст

В рамках схемы Yup это решается через transform:

import * as yup from "yup";

const schema = yup.object({
  username: yup
    .string()
    .transform((value) => {
      if (typeof value !== "string") return value;
      const trimmed = value.trim();
      return trimmed === "" ? null : trimmed;
    })
    .nullable()
});

Такой подход позволяет:

  • унифицировать пустые значения
  • избежать ложных срабатываний required
  • упростить downstream-логику

Приведение типов

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

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

age: yup
  .number()
  .transform((value, originalValue) => {
    if (originalValue === "") return null;
    const parsed = Number(originalValue);
    return Number.isNaN(parsed) ? undefined : parsed;
  })
  .nullable()

Ключевая цель — отделить:

  • пустое значение
  • некорректное значение
  • валидное число

Без этого разделения Yup начинает смешивать ошибки типов и обязательности поля.


Обработка дат

Дата — один из наиболее нестабильных типов данных в веб-формах.

Источники проблем:

  • строки в формате "YYYY-MM-DD"
  • локализованные строки
  • timestamp
  • пустые значения

Пример предобработки:

birthDate: yup
  .date()
  .transform((value, originalValue) => {
    if (!originalValue) return null;

    const date = new Date(originalValue);
    return isNaN(date.getTime()) ? undefined : date;
  })
  .nullable()

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


Использование transform как слоя предобработки

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

Основные особенности:

  • выполняется до валидации
  • влияет на значение, которое видит схема
  • может менять тип данных
  • может возвращать undefined, что трактуется как invalid

Пример комбинированной логики:

const schema = yup.object({
  price: yup
    .number()
    .transform((value, originalValue) => {
      if (typeof originalValue === "string") {
        const cleaned = originalValue.replace(",", ".");
        const parsed = parseFloat(cleaned);
        return isNaN(parsed) ? undefined : parsed;
      }
      return value;
    })
});

Предобработка на уровне YupResolver

Хотя YupResolver сам по себе не является инструментом трансформации данных, он влияет на поведение предобработки через параметры и стратегию выполнения схемы.

stripUnknown

Один из ключевых механизмов — удаление лишних полей:

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

const resolver = yupResolver(schema, {
  stripUnknown: true
});

Эффект:

  • из результата удаляются поля, не описанные в схеме
  • предотвращается утечка лишних данных в API
  • упрощается контракт формы

abortEarly

Параметр влияет на стратегию сбора ошибок:

yupResolver(schema, {
  abortEarly: false
});

Поведение:

  • true — остановка на первой ошибке
  • false — сбор всех ошибок

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


Превью данных перед валидацией

Под «превью» в контексте YupResolver обычно понимается промежуточное состояние данных перед финальной валидацией. Это важно для:

  • отладки схем
  • визуализации нормализации
  • анализа трансформаций

Получение промежуточного состояния

Поскольку YupResolver не предоставляет встроенный hook для preview, используется комбинация схемы и ручного вызова cast или validate.

const previewData = schema.cast(formValues, {
  stripUnknown: true
});

Метод cast позволяет увидеть:

  • как Yup интерпретирует входные данные
  • какие значения будут приведены к нужным типам
  • какие поля будут удалены

Отличие cast от validate

Ключевое различие:

  • cast — только преобразование
  • validate — преобразование + проверка правил
const casted = schema.cast(values);

const validated = await schema.validate(values);

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


Использование превью в логике формы

В связке с react-hook-form можно формировать «промежуточный слой анализа»:

const onCha nge = (values) => {
  const preview = schema.cast(values);

  console.log("PREVIEW:", preview);
};

Такой подход позволяет:

  • отслеживать поведение transform
  • проверять корректность нормализации
  • выявлять неожиданные преобразования типов

Детерминированность предобработки

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

YupResolver усиливает требования к детерминированности:

  • одинаковый input → одинаковый output
  • отсутствие скрытых побочных эффектов
  • предсказуемая трансформация типов

Для этого важно избегать:

  • зависимости transform от внешнего состояния
  • случайных значений внутри схемы
  • изменения входных данных вне Yup

Комбинирование preprocess-логики

В сложных формах предобработка редко ограничивается одним transform. Обычно она распределяется по слоям:

1. UI слой

  • trimming при onBlur
  • маски ввода
  • локальная нормализация

2. Resolver слой

  • yupResolver
  • stripUnknown
  • abortEarly стратегия

3. Schema слой

  • transform
  • default()
  • nullable()
  • type coercion

Такое разделение уменьшает связность и упрощает поддержку.


Обработка массивов и вложенных структур

Массивы требуют отдельного подхода, так как предобработка должна применяться рекурсивно.

const schema = yup.object({
  tags: yup
    .array()
    .of(
      yup
        .string()
        .transform((v) => (v?.trim() === "" ? null : v))
    )
});

Особенность:

  • transform применяется к каждому элементу
  • структура массива сохраняется
  • null-значения могут требовать дополнительной фильтрации

Предобработка и производительность

При больших формах предобработка становится значимым фактором производительности.

Проблемные сценарии:

  • глубокие вложенные схемы
  • частые re-render при каждом вводе
  • повторный cast/validate на каждое изменение

Оптимизационные подходы:

  • минимизация transform-логики
  • кэширование схем
  • разделение схем на части
  • использование lazy evaluation в Yup

Ошибки предобработки и их интерпретация

Некорректная предобработка часто проявляется не как ошибка схемы, а как «тихая деградация»:

  • поле становится undefined вместо ожидаемого значения
  • required не срабатывает из-за transform
  • число превращается в NaN и проходит дальше как invalid state

Типичный анти-паттерн:

.transform((v) => Number(v))

Без проверки это приводит к:

  • NaN в данных
  • непредсказуемым ошибкам на сервере

Корректный вариант требует явной обработки:

.transform((v) => {
  const n = Number(v);
  return Number.isNaN(n) ? undefined : n;
});

Согласованность между preview и финальной валидацией

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

  • cast (preview)
  • validate (final)

Если эти два слоя дают разные результаты, это сигнал о нарушении целостности схемы.

Причины расхождений:

  • условные transform через .when
  • внешние зависимости
  • различия в опциях resolver и validate

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