Именование и структура кода

Базовая организация слоя валидации

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

Основная цель структуры — отделить:

  • описание схемы валидации,
  • конфигурацию резолвера,
  • форму и UI-логику,
  • типы данных (при использовании TypeScript).

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


Принципы именования схем

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

Хорошая практика:

  • userSchema
  • authLoginSchema
  • profileUpdateSchema
  • orderCreateSchema

Плохая практика:

  • schema1
  • validationSchema
  • formSchemaNew

Важный момент заключается в том, что имя схемы должно совпадать с названием бизнес-сущности, а не формы. Например, если форма используется в двух разных UI-компонентах, схема остаётся одна.


Организация файлов схем

При использовании @hookform/resolvers с YupResolver схемы целесообразно выносить в отдельный слой.

Рекомендуемая структура:

src/
  validation/
    schemas/
      auth/
        login.schema.ts
        register.schema.ts
      user/
        profile.schema.ts
    resolvers/
      auth.resolvers.ts

Принцип построения:

  • один домен — одна директория,
  • одна схема — один файл,
  • именование файлов строго по назначению.

Файл схемы не должен содержать UI-логики или зависимостей от компонентов.


Именование полей внутри схем

Поле в схеме должно соответствовать данным, которые приходят с формы или API. Несоответствие имен приводит к дополнительным маппингам и усложняет поддержку.

Пример корректного описания:

import * as yup from "yup";

export const loginSchema = yup.object({
  email: yup.string().email().required(),
  password: yup.string().min(8).required(),
});

Здесь:

  • email и password совпадают с именами input-полей,
  • нет абстрактных ключей вроде field1 или inputA.

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


Именование резолверов

YupResolver используется как адаптер между схемой и формой:

import { yupResolver } from "@hookform/resolvers/yup";
import { loginSchema } from "../schemas/auth/login.schema";

export const loginResolver = yupResolver(loginSchema);

Правила именования:

  • loginResolver — для конкретной формы,
  • authLoginResolver — если требуется уточнение домена,
  • избегать общего resolver.

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


Связь схемы, резолвера и формы

Структурно связь должна быть однонаправленной:

schema → resolver → form

Форма не должна знать о деталях схемы, кроме передачи в useForm.

Пример:

import { useForm } from "react-hook-form";
import { loginResolver } from "../validation/resolvers/auth.resolvers";

export function LoginForm() {
  const form = useForm({
    resolver: loginResolver,
  });

  return null;
}

Такой подход исключает смешивание уровней ответственности.


Единый стиль именования ошибок

Ошибки, возвращаемые Yup через YupResolver, автоматически маппятся в структуру formState.errors.

Важно поддерживать единый стиль обращения:

  • errors.email
  • errors.password

Не рекомендуется:

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

Если требуется трансформация сообщений, она должна быть частью схемы:

email: yup
  .string()
  .email("Некорректный формат email")
  .required("Email обязателен");

Структура сложных схем

При увеличении количества полей схема должна сохранять модульность.

Подход — декомпозиция:

const passwordRules = yup.string().min(8).required();

export const registerSchema = yup.object({
  email: yup.string().email().required(),
  password: passwordRules,
  confirmPassword: yup
    .string()
    .oneOf([yup.ref("password")])
});

Общие правила:

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

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

При работе с объектами важно сохранять иерархию данных:

const addressSchema = yup.object({
  city: yup.string().required(),
  street: yup.string().required(),
});

export const userSchema = yup.object({
  name: yup.string().required(),
  address: addressSchema,
});

Имена:

  • addressSchema, а не schemaAddress,
  • вложенные структуры всегда отражают предметную область.

Избежание дублирования логики

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

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

export const createUserSchema = yup.object({
  ...baseUserSchema,
  password: yup.string().min(8).required(),
});

export const updateUserSchema = yup.object({
  ...baseUserSchema,
});

Такое разделение снижает риск рассинхронизации требований.


Контекстное именование в больших проектах

В крупных кодовых базах одного имени недостаточно. Тогда применяется доменная приставка:

  • authLoginSchema
  • billingInvoiceSchema
  • profileSettingsSchema

Принцип:

  • первая часть — домен,
  • вторая — действие или сущность,
  • третья — тип (schema/resolver при необходимости).

Это снижает вероятность конфликтов имён при масштабировании.


Стандартизация структуры модуля валидации

Стабильная единица модуля выглядит следующим образом:

auth/
  login/
    login.schema.ts
    login.resolver.ts
    login.types.ts

Каждый элемент отвечает за одну задачу:

  • schema — правила,
  • resolver — интеграция с формой,
  • types — контракт данных.

Такой подход обеспечивает предсказуемость и упрощает навигацию по проекту без необходимости анализа зависимостей в рантайме.