Кеширование схем

Повторное использование экземпляров схем

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

Наиболее распространённый подход — вынесение схемы на уровень модуля:

import * as yup from 'yup';

export const userSchema = yup.object({
  name: yup.string().required(),
  email: yup.string().email().required(),
  age: yup.number().min(18),
});

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


Проблема пересоздания схем

Частая ошибка — создание схемы внутри функций компонентов или обработчиков:

function validate(data) {
  const schema = yup.object({
    name: yup.string().required(),
  });

  return schema.validate(data);
}

При каждом вызове создаётся новый экземпляр схемы, что приводит к:

  • лишней нагрузке на GC (сборщик мусора),
  • увеличению времени выполнения при частых вызовах,
  • деградации производительности в формах.

Особенно это критично в React-приложениях, где рендеры происходят часто.


Мемоизация фабрик схем

В случаях, когда схема зависит от параметров, используется фабрика схем. Для предотвращения повторной генерации применяется мемоизация.

import * as yup from 'yup';

const schemaCache = new Map();

export function getUserSchema(withAge) {
  const key = String(withAge);

  if (schemaCache.has(key)) {
    return schemaCache.get(key);
  }

  const schema = yup.object({
    name: yup.string().required(),
    ...(withAge ? { age: yup.number().min(18) } : {}),
  });

  schemaCache.set(key, schema);
  return schema;
}

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

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

Кеширование через замыкания

Более компактный вариант мемоизации — использование замыканий:

const createSchema = (() => {
  let cache = null;

  return () => {
    if (cache) return cache;

    cache = yup.object({
      email: yup.string().email().required(),
    });

    return cache;
  };
})();

Такой способ подходит для статических схем, которые не зависят от внешних параметров.


WeakMap для контекстного кеширования

При работе с динамическими конфигурациями удобно использовать WeakMap, особенно если ключами выступают объекты конфигурации.

import * as yup from 'yup';

const schemaCache = new WeakMap();

export function getSchema(config) {
  if (schemaCache.has(config)) {
    return schemaCache.get(config);
  }

  const schema = yup.object({
    name: yup.string().required(config.requiredName),
  });

  schemaCache.set(config, schema);
  return schema;
}

Преимущества:

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

Кеширование динамических схем (lazy и when)

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

lazy-схемы

const schema = yup.lazy((value) => {
  if (typeof value === 'string') {
    return yup.string().min(3);
  }

  return yup.object({
    id: yup.number().required(),
  });
});

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


when-схемы

const schema = yup.object({
  type: yup.string(),
  value: yup.mixed().when('type', {
    is: 'number',
    then: yup.number(),
    otherwise: yup.string(),
  }),
});

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


Клонирование схем и его влияние на кеш

Yup предоставляет метод clone(), который создаёт новый экземпляр схемы:

const baseSchema = yup.object({
  email: yup.string().email(),
});

const schemaCopy = baseSchema.clone();

Клонирование полезно при необходимости модификации схемы без изменения оригинала, однако оно:

  • полностью разрушает преимущества кеширования,
  • создаёт новый граф валидаторов,
  • увеличивает потребление памяти.

Поэтому clone() следует использовать только при реальной необходимости изоляции схем.


Кеширование валидации результатов

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

Пример ключа кеша на основе входных данных:

import * as yup from 'yup';

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

const resultCache = new Map();

async function validate(data) {
  const key = JSON.stringify(data);

  if (resultCache.has(key)) {
    return resultCache.get(key);
  }

  const result = await schema.validate(data);
  resultCache.set(key, result);

  return result;
}

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


Кеширование в React-архитектуре форм

В UI-слоях схемы часто передаются в компоненты форм. Ошибкой является создание схемы внутри компонента:

function Form() {
  const schema = yup.object({
    name: yup.string().required(),
  });

  return null;
}

При каждом рендере создаётся новая схема, что может приводить к:

  • сбросу состояния валидации,
  • повторным вычислениям ошибок,
  • лишним ререндерам.

Правильный подход — использование useMemo:

function Form() {
  const schema = useMemo(() => {
    return yup.object({
      name: yup.string().required(),
    });
  }, []);

  return null;
}

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

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

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

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

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


Кеширование валидационных цепочек

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

Оптимизация достигается через:

  • вынесение общих валидаторов,
  • использование фабрик для повторяющихся правил,
  • сохранение базовых схем как строительных блоков.
const requiredString = yup.string().required();

const profileSchema = yup.object({
  firstName: requiredString,
  lastName: requiredString,
});

Архитектурный подход к кешированию схем

В масштабных приложениях схемы обычно организуются как слой конфигурации:

  • базовые схемы (primitive schemas),
  • составные схемы (composed schemas),
  • динамические схемы (factory + cache),
  • runtime-конфигурации (WeakMap или Map).

Такая структура позволяет:

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