Работа с большим количеством полей

Массовая работа с инстансами Cleave.js требует продуманной архитектуры, поскольку каждая инициализация добавляет обработчики событий, хранит внутреннее состояние и увеличивает нагрузку на DOM. При десятках и сотнях полей неправильная стратегия приводит к падению производительности, утечкам памяти и сложностям сопровождения.

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

const cleaveInstances = new Map();

function createCleave(id, options) {
  const element = document.getElementById(id);
  if (!element) return;

  const instance = new Cleave(element, options);
  cleaveInstances.set(id, instance);

  return instance;
}

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

Групповая инициализация полей

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

<input id="phone1" data-cleave="phone">
<input id="phone2" data-cleave="phone">
<input id="date1" data-cleave="date">
const configs = {
  phone: {
    phone: true,
    phoneRegionCode: 'KZ'
  },
  date: {
    date: true,
    datePattern: ['d', 'm', 'Y']
  }
};

document.querySelectorAll('[data-cleave]').forEach((el) => {
  const type = el.dataset.cleave;
  const options = configs[type];

  if (options) {
    const instance = new Cleave(el, options);
    cleaveInstances.set(el.id, instance);
  }
});

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

Делегирование и динамические поля

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

Оптимальный подход — инициализация только новых элементов:

function initCleaveForContainer(container) {
  container.querySelectorAll('[data-cleave]').forEach((el) => {
    if (cleaveInstances.has(el.id)) return;

    const type = el.dataset.cleave;
    const instance = new Cleave(el, configs[type]);

    cleaveInstances.set(el.id, instance);
  });
}

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

Удаление и освобождение ресурсов

При удалении DOM-элементов необходимо синхронно уничтожать связанные инстансы. Cleave.js не всегда автоматически освобождает память, если ссылка на объект сохраняется.

function destroyCleave(id) {
  const instance = cleaveInstances.get(id);
  if (instance) {
    instance.destroy();
    cleaveInstances.delete(id);
  }
}

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

Оптимизация массовых обновлений

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

Прямое пересоздание каждого поля может быть дорогим. Более эффективная стратегия — частичное обновление через destroy + recreate только для затронутых типов.

function updateByType(type, newOptions) {
  cleaveInstances.forEach((instance, id) => {
    if (instance.properties && instance.properties.type === type) {
      instance.destroy();

      const el = document.getElementById(id);
      const newInstance = new Cleave(el, newOptions);

      cleaveInstances.set(id, newInstance);
    }
  });
}

Минимизация нагрузки при вводе

При большом количестве активных полей важно учитывать частоту внутренних обработчиков Cleave.js. Хотя библиотека оптимизирована, десятки одновременно активных input-событий могут создавать заметную нагрузку.

Практика снижения нагрузки:

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

Пример ленивой инициализации:

function lazyInit(el) {
  let instance = null;

  el.addEventListener('focus', () => {
    if (!instance) {
      instance = new Cleave(el, configs[el.dataset.cleave]);
      cleaveInstances.set(el.id, instance);
    }
  });

  el.addEventListener('blur', () => {
    if (instance && el.dataset.lazy === 'true') {
      instance.destroy();
      cleaveInstances.delete(el.id);
      instance = null;
    }
  });
}

Переиспользование конфигураций

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

const sharedConfigs = Object.freeze({
  phone: {
    phone: true,
    phoneRegionCode: 'KZ'
  },
  currency: {
    numeral: true,
    numeralThousandsGroupStyle: 'thousand'
  },
  date: {
    date: true,
    datePattern: ['d', 'm', 'Y']
  }
});

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

Работа с виртуальными списками

В интерфейсах с виртуализацией (например, таблицы с прокруткой) поля постоянно монтируются и размонтируются. В таких условиях важно привязывать жизненный цикл Cleave.js к жизненному циклу строки.

function onRowRender(row) {
  row.querySelectorAll('[data-cleave]').forEach((el) => {
    const instance = new Cleave(el, configs[el.dataset.cleave]);
    cleaveInstances.set(el.dataset.key, instance);
  });
}

function onRowRemove(row) {
  row.querySelectorAll('[data-cleave]').forEach((el) => {
    const instance = cleaveInstances.get(el.dataset.key);
    if (instance) {
      instance.destroy();
      cleaveInstances.delete(el.dataset.key);
    }
  });
}

Предотвращение утечек памяти

Основные источники утечек:

  • сохранённые ссылки в Map без удаления
  • незавершённые обработчики событий
  • повторная инициализация без destroy

Контроль состояния:

window.addEventListener('beforeunload', () => {
  cleaveInstances.forEach((instance) => instance.destroy());
  cleaveInstances.clear();
});

Масштабирование архитектуры

При сотнях полей становится оправданным введение слоя абстракции — менеджера форматов.

class CleaveManager {
  constructor() {
    this.instances = new Map();
    this.configs = sharedConfigs;
  }

  init(el) {
    const type = el.dataset.cleave;
    const instance = new Cleave(el, this.configs[type]);

    this.instances.set(el.id, instance);
  }

  destroy(id) {
    const instance = this.instances.get(id);
    if (!instance) return;

    instance.destroy();
    this.instances.delete(id);
  }

  destroyAll() {
    this.instances.forEach((i) => i.destroy());
    this.instances.clear();
  }
}

Такой слой упрощает тестирование, масштабирование и интеграцию с фреймворками.

Поведенческие ограничения при большом количестве полей

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

  • увеличение времени первичной инициализации
  • рост стоимости reflow при массовом обновлении DOM
  • конкуренция обработчиков input-событий
  • увеличение задержек при синхронном форматировании

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

Стратегия частичной активации

Эффективный подход — активировать форматирование только в активной области интерфейса.

function activateSection(section) {
  section.querySelectorAll('[data-cleave]').forEach((el) => {
    if (!cleaveInstances.has(el.id)) {
      const instance = new Cleave(el, configs[el.dataset.cleave]);
      cleaveInstances.set(el.id, instance);
    }
  });
}

function deactivateSection(section) {
  section.querySelectorAll('[data-cleave]').forEach((el) => {
    const instance = cleaveInstances.get(el.id);
    if (instance) {
      instance.destroy();
      cleaveInstances.delete(el.id);
    }
  });
}

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