Обработка множественных полей

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

Базовый принцип заключается в том, что каждый DOM-элемент получает собственный экземпляр Cleave, а управление этими экземплярами выносится в слой логики приложения.

const instances = [];

document.querySelectorAll('.phone-input').forEach((input) => {
  const cleave = new Cleave(input, {
    phone: true,
    phoneRegionCode: 'US'
  });

  instances.push(cleave);
});

В такой структуре массив instances становится центральной точкой контроля над всеми форматируемыми полями.


Идентификация и связывание экземпляров с данными

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

const state = {
  phones: []
};

const instances = {};

document.querySelectorAll('.phone-input').forEach((input, index) => {
  instances[index] = new Cleave(input, {
    phone: true,
    onValueChanged: (e) => {
      state.phones[index] = e.target.value;
    }
  });
});

Здесь важно, что каждое поле не только форматирует ввод, но и синхронизирует состояние с внешним хранилищем.

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


Динамическое добавление полей

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

function addPhoneField(container) {
  const input = document.createElement('input');
  input.className = 'phone-input';
  container.appendChild(input);

  const cleave = new Cleave(input, {
    phone: true,
    phoneRegionCode: 'US'
  });

  instances.push(cleave);
}

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


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

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

function removeField(index) {
  const cleave = instances[index];

  if (cleave) {
    cleave.destroy();
    delete instances[index];
  }

  const input = document.querySelectorAll('.phone-input')[index];
  if (input) {
    input.remove();
  }
}

Метод destroy() играет ключевую роль в предотвращении утечек памяти при работе с динамическими формами.


Стабильные ключи вместо индексов

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

const instances = {};
const state = {};

function createField(id) {
  const input = document.createElement('input');
  input.dataset.id = id;

  document.body.appendChild(input);

  instances[id] = new Cleave(input, {
    phone: true,
    onValueChanged: (e) => {
      state[id] = e.target.value;
    }
  });
}

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


Синхронизация группы полей

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

const cardParts = {
  part1: new Cleave('#card1', { blocks: [4] }),
  part2: new Cleave('#card2', { blocks: [4] }),
  part3: new Cleave('#card3', { blocks: [4] }),
  part4: new Cleave('#card4', { blocks: [4] })
};

function getCardNumber() {
  return Object.values(cardParts)
    .map(instance => instance.getRawValue())
    .join('');
}

Здесь важно различать отображаемое значение и «сырое» значение, возвращаемое методом getRawValue(). Это позволяет корректно собирать данные независимо от форматирования.


Управление общими настройками формата

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

function createCleave(input, type) {
  const configs = {
    phone: {
      phone: true,
      phoneRegionCode: 'US'
    },
    date: {
      date: true,
      datePattern: ['d', 'm', 'Y']
    },
    card: {
      creditCard: true
    }
  };

  return new Cleave(input, configs[type]);
}

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


Обработка событий на уровне формы

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

function collectValues() {
  return Object.entries(instances).reduce((acc, [key, instance]) => {
    acc[key] = instance.getRawValue();
    return acc;
  }, {});
}

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


Обновление конфигурации для группы экземпляров

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

function updateRegion(regionCode) {
  Object.values(instances).forEach((cleave) => {
    cleave.setPhoneRegionCode(regionCode);
  });
}

Хотя Cleave.js не всегда предоставляет полный набор динамических методов для всех типов масок, общий подход заключается в пересоздании экземпляра при изменении критичных параметров.


Пересоздание экземпляров при изменении структуры формы

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

function resetInstances() {
  Object.values(instances).forEach((cleave) => cleave.destroy());

  Object.keys(instances).forEach((key) => delete instances[key]);

  document.querySelectorAll('.phone-input').forEach((input, index) => {
    instances[index] = new Cleave(input, {
      phone: true
    });
  });
}

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


Интеграция с реактивными структурами данных

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

const formState = {
  values: {},
  cleaveRefs: {}
};

function bindField(id, input) {
  formState.cleaveRefs[id] = new Cleave(input, {
    numeral: true,
    onValueChanged: (e) => {
      formState.values[id] = e.target.value;
    }
  });
}

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


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

При десятках и сотнях полей важно учитывать производительность. Основные факторы влияния:

  • количество активных обработчиков onValueChanged
  • частота пересчёта форматирования
  • повторная инициализация при рендере

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

  • ленивую инициализацию при фокусе
  • уничтожение неиспользуемых экземпляров
  • группировку обновлений состояния
function lazyInit(input) {
  let cleave;

  input.addEventListener('focus', () => {
    if (!cleave) {
      cleave = new Cleave(input, {
        numeral: true
      });
    }
  });
}

Контроль жизненного цикла экземпляров

Управление множественными полями требует строгого контроля жизненного цикла:

  • создание при монтировании DOM
  • обновление при изменении структуры
  • уничтожение при удалении узла

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

function unmountAll() {
  Object.values(instances).forEach((cleave) => {
    cleave.destroy();
  });

  Object.keys(instances).forEach(key => delete instances[key]);
}

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