Массивы: array schema

Схема массива в Yup строится вокруг базового метода array(), который позволяет описывать структуру и ограничения для массивов любой сложности: от простых списков строк до вложенных коллекций объектов с многоуровневой валидацией.

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


Схема массива создаётся через Yup.array():

import * as Yup from 'yup';

const schema = Yup.array();

Такое объявление само по себе не накладывает ограничений на содержимое массива. Любые значения, включая undefined и null, будут допустимы, если не добавлены дополнительные правила.


Определение типа элементов массива

Ключевой метод — of(), который задаёт схему для каждого элемента массива:

const schema = Yup.array().of(
  Yup.string()
);

В этом случае массив должен содержать только строки. Любое несоответствие типу приведёт к ошибке валидации.

Пример допустимых данных:

schema.validate(['a', 'b', 'c']);

Пример недопустимых данных:

schema.validate(['a', 2, true]);

Обязательность массива

Для указания обязательного массива используется required():

const schema = Yup.array().of(
  Yup.number()
).required();

Теперь значение undefined или null будет считаться ошибкой, даже если структура элементов корректна.


Ограничение длины массива

Yup предоставляет методы для контроля размера массива:

Минимальная длина

Yup.array().min(2);

Максимальная длина

Yup.array().max(5);

Точная длина

Yup.array().length(3);

Эти ограничения часто применяются в формах, где количество элементов строго регламентировано, например список контактов или выбор вариантов.


Валидация уникальности элементов

Метод unique отсутствует в базовом Yup, но реализуется через test():

const schema = Yup.array().test(
  'unique',
  'Элементы массива должны быть уникальными',
  (value) => {
    if (!value) return true;
    return new Set(value).size === value.length;
  }
);

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


Массив объектов

Наиболее распространённый сценарий — массив объектов с собственной схемой:

const itemSchema = Yup.object({
  id: Yup.number().required(),
  name: Yup.string().required()
});

const schema = Yup.array().of(itemSchema);

Пример данных:

[
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]

Любое нарушение структуры объекта внутри массива приведёт к ошибке именно на уровне элемента, а не всего массива.


Вложенные массивы

Yup позволяет строить многоуровневые структуры:

const schema = Yup.array().of(
  Yup.array().of(
    Yup.number().required()
  )
);

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


Смешанные типы элементов

При необходимости допускается использование mixed():

const schema = Yup.array().of(
  Yup.mixed()
);

Это отключает строгую типизацию, но позволяет добавлять проверки через test():

const schema = Yup.array().of(
  Yup.mixed().test('is-valid', 'Недопустимое значение', (value) => {
    return typeof value === 'string' || typeof value === 'number';
  })
);

Проверка каждого элемента через test()

Метод test() внутри of() позволяет реализовать кастомную логику:

const schema = Yup.array().of(
  Yup.number().test(
    'positive',
    'Число должно быть положительным',
    (value) => value > 0
  )
);

Каждый элемент проходит проверку независимо.


Комбинация условий (when)

Условная логика позволяет менять схему массива в зависимости от внешних данных:

const schema = Yup.array().when('type', {
  is: 'numbers',
  then: Yup.array().of(Yup.number()),
  otherwise: Yup.array().of(Yup.string())
});

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


Преобразование данных (transform)

Метод transform() позволяет нормализовать входные значения:

const schema = Yup.array().transform((value) => {
  if (typeof value === 'string') {
    return value.split(',');
  }
  return value;
});

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


Работа с пустыми значениями

Пустые массивы часто требуют отдельной обработки:

Yup.array()
  .of(Yup.string())
  .min(1, 'Массив не должен быть пустым');

Также можно явно разрешать пустое значение:

Yup.array().nullable();

Валидация асинхронных данных внутри массива

При использовании test() возможно выполнение асинхронных проверок:

const schema = Yup.array().of(
  Yup.string().test(
    'exists',
    'Значение не найдено',
    async (value) => {
      const result = await fakeApiCheck(value);
      return result === true;
    }
  )
);

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


Работа с типизацией (TypeScript)

Yup поддерживает вывод типов:

import * as Yup from 'yup';

const schema: Yup.ArraySchema<string[]> = Yup.array().of(
  Yup.string().required()
);

Для объектов:

type User = {
  id: number;
  name: string;
};

const schema: Yup.ArraySchema<User[]> = Yup.array().of(
  Yup.object({
    id: Yup.number().required(),
    name: Yup.string().required()
  })
);

Поведение при ошибках

Ошибки валидации массива могут быть:

  • глобальными (для всего массива)
  • точечными (для конкретного индекса)

Пример:

[0].name must be a string

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


Композиция схем массивов

Схемы можно переиспользовать:

const stringArray = Yup.array().of(Yup.string());

const emailArray = Yup.array().of(
  Yup.string().email()
);

Это упрощает поддержку больших проектов и снижает дублирование логики.


Глубокая вложенность и производительность

При работе с вложенными массивами и сложными объектами важно учитывать стоимость валидации. Каждое of() создаёт отдельный уровень обхода структуры данных, что при больших объёмах может влиять на производительность.

Пример глубокой структуры:

Yup.array().of(
  Yup.object({
    tags: Yup.array().of(
      Yup.object({
        value: Yup.string().required()
      })
    )
  })
);

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


Расширение логики через кастомные схемы

В сложных случаях массивы комбинируются с lazy() для динамического определения структуры:

const schema = Yup.array().of(
  Yup.lazy((value) => {
    if (typeof value === 'string') {
      return Yup.string().min(3);
    }
    return Yup.number().positive();
  })
);

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


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

Массивы часто используются внутри объектов:

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

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