При работе с формами, содержащими повторяющиеся элементы ввода, ключевым становится управление набором независимых экземпляров форматирования. В 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
});
}
});
}
Управление множественными полями требует строгого контроля жизненного цикла:
Несоблюдение этих этапов приводит к накоплению неиспользуемых обработчиков и неконсистентности данных.
function unmountAll() {
Object.values(instances).forEach((cleave) => {
cleave.destroy();
});
Object.keys(instances).forEach(key => delete instances[key]);
}
Такой подход обеспечивает полное освобождение ресурсов при смене состояния интерфейса.