Добавление глобальных методов

Библиотека Yup предоставляет механизм расширения стандартных схем через добавление пользовательских методов на уровне прототипов. Это позволяет централизованно определять повторно используемую логику валидации и интегрировать её во все экземпляры схем без дублирования кода.

Каждый тип схемы в Yup (string, number, object, array и др.) наследуется от базового класса Schema. Именно этот уровень используется для добавления глобальных методов, доступных во всех экземплярах соответствующего типа.

Механизм расширения строится вокруг функции addMethod, которая привязывает новый метод к прототипу схемы:

import * as Yup from 'yup';

Yup.addMethod(Yup.string, 'onlyLetters', function (message) {
  return this.test('only-letters', message, function (value) {
    const { path, createError } = this;

    if (!value) return true;

    return /^[A-Za-zА-Яа-я]+$/.test(value)
      ? true
      : createError({ path, message });
  });
});

После регистрации метод становится доступен для всех строковых схем:

const schema = Yup.string().onlyLetters('Допустимы только буквы');

Механизм работы addMethod

Функция addMethod принимает три параметра:

  • целевой тип схемы (например, Yup.string)
  • имя метода
  • функцию-реализацию

Внутри происходит расширение прототипа, поэтому все последующие экземпляры автоматически получают новый метод.

Yup.addMethod(schemaType, methodName, methodImplementation);

Ключевой особенностью является контекст this, который внутри метода указывает на текущую схему. Это позволяет комбинировать встроенные методы Yup с пользовательскими.

Создание универсальных валидаторов

Глобальные методы часто используются для стандартизации правил валидации в рамках приложения. Например, проверка диапазона длины строки:

Yup.addMethod(Yup.string, 'betweenLength', function (min, max, message) {
  return this.min(min, message).max(max, message);
});

Использование:

const schema = Yup.string().betweenLength(5, 20, 'Недопустимая длина');

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

Добавление методов для числовых схем

Глобальные методы могут быть специфичны для типа данных. Для чисел часто добавляются проверки бизнес-логики:

Yup.addMethod(Yup.number, 'isEven', function (message) {
  return this.test('is-even', message, function (value) {
    if (value === undefined || value === null) return true;

    return value % 2 === 0;
  });
});

Применение:

const schema = Yup.number().isEven('Число должно быть чётным');

Композиция пользовательских методов

Глобальные методы можно комбинировать с уже существующими методами Yup. Это позволяет строить сложные правила без потери читаемости:

Yup.addMethod(Yup.string, 'strictEmail', function () {
  return this.email().trim().lowercase();
});

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

Работа с объектными схемами

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

Yup.addMethod(Yup.object, 'atLeastOneRequired', function (fields, message) {
  return this.test('at-least-one-required', message, function (value) {
    const { path, createError } = this;

    const hasValue = fields.some(
      (field) => value && value[field] !== undefined && value[field] !== ''
    );

    return hasValue
      ? true
      : createError({ path, message });
  });
});

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

const schema = Yup.object({
  phone: Yup.string(),
  email: Yup.string(),
}).atLeastOneRequired(['phone', 'email'], 'Необходимо заполнить хотя бы одно поле');

Изоляция и предотвращение конфликтов

При добавлении глобальных методов важно учитывать возможные конфликты имён. Повторное определение метода приведёт к перезаписи поведения. Для минимизации рисков используются:

  • префиксы в названиях методов
  • неймспейсы через объект-обёртку
  • централизованный файл расширений
Yup.addMethod(Yup.string, 'app_onlyLetters', function () {
  return this.matches(/^[A-Za-z]+$/);
});

TypeScript расширения

При использовании TypeScript требуется явное расширение типов, иначе новые методы не будут распознаны системой типов.

declare module 'yup' {
  interface StringSchema {
    onlyLetters(message?: string): StringSchema;
  }
}

Это позволяет сохранять типобезопасность при работе с расширенными схемами.

Переиспользуемые модули расширений

В крупных проектах глобальные методы выносятся в отдельные модули:

// validators/string.js
import * as Yup from 'yup';

export function registerStringValidators() {
  Yup.addMethod(Yup.string, 'noSpaces', function (message) {
    return this.test('no-spaces', message, value => {
      if (!value) return true;
      return !/\s/.test(value);
    });
  });
}

Инициализация выполняется один раз при запуске приложения, после чего методы становятся доступны во всех схемах.

Поведение при цепочках методов

Глобальные методы органично встраиваются в цепочки Yup:

const schema = Yup.string()
  .required()
  .trim()
  .noSpaces()
  .min(5);

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

Ограничения расширения

Несмотря на гибкость, механизм глобальных методов имеет ряд ограничений:

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

Эти факторы делают целесообразным централизованный подход к регистрации расширений и строгую структуру именования методов