Базовая модель работы библиотеки строится вокруг связывания
экземпляра форматтера с конкретным HTMLInputElement. После
инициализации экземпляр начинает перехватывать пользовательский ввод,
преобразовывать значение поля и поддерживать синхронизацию между
отображаемым значением и «сырым» значением данных.
Основной принцип заключается в том, что Cleave не заменяет стандартное поведение input, а дополняет его, вмешиваясь в процесс изменения значения.
Минимальная форма привязки предполагает передачу DOM-элемента в конструктор:
import Cleave from 'cleave.js';
const input = document.querySelector('#phone');
const cleave = new Cleave(input, {
phone: true,
phoneRegionCode: 'KZ'
});
В этом случае экземпляр получает прямую ссылку на элемент и начинает
отслеживать события ввода. При каждом изменении значения выполняется
перерасчёт форматирования, после чего обновляется value у
input.
Ключевой момент: библиотека не копирует значение в отдельное состояние, а работает поверх DOM-узла.
Допустим сценарий, когда элемент не требуется заранее сохранять в переменную:
new Cleave('#credit-card', {
creditCard: true
});
Внутри происходит поиск элемента через
document.querySelector. Это упрощённая форма инициализации,
которая используется для одиночных элементов без дополнительной логики
управления.
При этом важно учитывать, что повторная инициализация на одном и том же селекторе без удаления предыдущего экземпляра приводит к наложению обработчиков событий.
При работе с группами полей требуется отдельная инициализация для каждого элемента. Cleave не выполняет автоматическую привязку к списку узлов.
const inputs = document.querySelectorAll('.date-field');
inputs.forEach((el) => {
new Cleave(el, {
date: true,
datePattern: ['d', 'm', 'Y']
});
});
Каждый input получает собственный экземпляр. Это означает изоляцию состояния: форматирование одного поля не влияет на другое.
После создания экземпляра происходит установка внутренних обработчиков событий:
inputkeydownfocuscutcopypasteФактический список зависит от конфигурации и версии библиотеки, однако логика всегда строится вокруг контроля изменения значения поля.
Привязка сохраняется до тех пор, пока экземпляр не будет уничтожен вручную:
cleave.destroy();
После вызова destroy происходит:
В приложениях с динамическим DOM input может появляться после загрузки страницы. В таком случае привязка выполняется после вставки элемента в DOM-дерево.
function createPhoneInput() {
const input = document.createElement('input');
input.id = 'dynamic-phone';
document.body.appendChild(input);
new Cleave(input, {
phone: true,
phoneRegionCode: 'KZ'
});
}
Ключевое условие — элемент должен существовать в DOM в момент передачи в конструктор. Попытка инициализации до вставки приводит к отсутствию корректной привязки.
Частая проблема при интеграции — повторное создание экземпляра на одном и том же input. Это происходит, например, при повторном рендере компонента или повторном вызове функции инициализации.
const input = document.querySelector('#amount');
if (!input.cleaveInstance) {
input.cleaveInstance = new Cleave(input, {
numeral: true,
numeralThousandsGroupStyle: 'thousand'
});
}
Такой подход позволяет сохранять ссылку на экземпляр и предотвращать наложение обработчиков.
Альтернативный подход — явное уничтожение перед повторной инициализацией:
if (input.cleaveInstance) {
input.cleaveInstance.destroy();
}
input.cleaveInstance = new Cleave(input, {
numeral: true
});
Иногда форматирование требуется только при взаимодействии с полем, а
не сразу при загрузке страницы. В этом случае инициализация
откладывается до события focus.
const input = document.querySelector('#lazy-format');
let cleaveInstance = null;
input.addEventListener('focus', () => {
if (!cleaveInstance) {
cleaveInstance = new Cleave(input, {
date: true,
datePattern: ['d', 'm', 'Y']
});
}
});
Такой подход снижает количество активных экземпляров и позволяет контролировать момент активации форматирования.
Cleave работает напрямую с value DOM-элемента. Это
означает, что любые внешние изменения через JavaScript должны учитывать
возможное вмешательство форматтера.
input.value = '1234567890';
input.dispatchEvent(new Event('input'));
Без генерации события input библиотека может не
синхронизировать внутреннее состояние с новым значением.
В интерфейсах, где DOM может полностью перерисовываться (например, SPA), важно учитывать потерю привязки.
При удалении input из DOM:
Типовой паттерн:
let cleaveInstance = null;
function mount() {
const input = document.querySelector('#spa-input');
cleaveInstance = new Cleave(input, {
numeral: true
});
}
function unmount() {
if (cleaveInstance) {
cleaveInstance.destroy();
cleaveInstance = null;
}
}
Сброс формы через form.reset() не всегда приводит к
корректному обновлению состояния форматтера. В таких случаях требуется
принудительная синхронизация:
const form = document.querySelector('form');
const input = document.querySelector('#price');
const cleave = new Cleave(input, {
numeral: true
});
form.addEventListener('reset', () => {
setTimeout(() => {
cleave.setRawValue('');
}, 0);
});
Метод setRawValue позволяет установить «чистое» значение
без форматирования и восстановить согласованное состояние между DOM и
внутренним состоянием библиотеки.
Технически возможно создать несколько экземпляров Cleave на одном элементе, но поведение становится непредсказуемым:
new Cleave('#input', { numeral: true });
new Cleave('#input', { creditCard: true });
Каждый экземпляр устанавливает свои обработчики и перезаписывает поведение ввода. В результате:
Корректная архитектура предполагает строго один активный экземпляр на один input.
При масштабных проектах обычно вводится единая функция инициализации:
function bindCleave(el, config) {
if (el.cleaveInstance) {
el.cleaveInstance.destroy();
}
el.cleaveInstance = new Cleave(el, config);
return el.cleaveInstance;
}
bindCleave(document.querySelector('#phone'), {
phone: true,
phoneRegionCode: 'KZ'
});
Такой подход стандартизирует привязку и предотвращает дублирование логики по всему приложению.
Некоторые реализации используют данные из HTML-атрибутов для конфигурации:
<input id="phone" data-format="phone" data-region="KZ">
const input = document.querySelector('#phone');
const format = input.dataset.format;
const region = input.dataset.region;
new Cleave(input, {
phone: format === 'phone',
phoneRegionCode: region
});
Такой подход позволяет отделить конфигурацию от JavaScript-логики и централизовать поведение на уровне разметки.
При наличии сторонних слушателей на input важно учитывать порядок выполнения:
valueinput.addEventListener('input', (e) => {
console.log('formatted:', e.target.value);
});
Фактическое значение в момент события уже модифицировано библиотекой, что критично для логики валидации и отправки данных.
Для удобства управления экземпляр часто сохраняется в свойстве DOM-элемента:
const input = document.querySelector('#sum');
input.cleaveInstance = new Cleave(input, {
numeral: true
});
Это упрощает доступ из разных частей приложения без необходимости хранить отдельные реестры экземпляров.
При этом сохраняется риск утечки памяти при удалении DOM без вызова
destroy, поэтому управление жизненным циклом должно
оставаться явным.