При использовании Cleave.js основная сложность заключается не в самом форматировании, а в согласовании внутреннего состояния приложения с тем, что отображается в поле ввода. Библиотека работает как слой трансформации между «сырыми» данными пользователя и их визуальным представлением, что автоматически порождает необходимость чётко разделять два вида состояния: исходное значение и форматированное представление.
Ключевой принцип работы заключается в том, что Cleave.js не является источником истины для данных приложения. Он управляет отображением, но не диктует структуру состояния. Истина должна находиться либо в DOM-инпуте (в неконтролируемом режиме), либо во внешнем состоянии (в React, Vue, Svelte и аналогичных системах).
В основе архитектуры состояния при работе с Cleave.js лежит различие между двумя сущностями:
rawValue — значение без форматирования, пригодное для хранения, отправки на сервер и бизнес-логики.
formattedValue — отображаемая строка, содержащая маски, разделители, символы валюты и прочие визуальные элементы.
Cleave.js предоставляет доступ к обоим уровням через callback-механизм и методы экземпляра. Это позволяет строить предсказуемую модель данных, где UI не влияет на семантику значения.
Типичный сценарий обновления состояния выглядит следующим образом: пользователь вводит данные → Cleave.js преобразует ввод → вызывается событие обновления → приложение синхронизирует rawValue со своим состоянием → при необходимости пересчитывается UI.
При создании экземпляра Cleave.js состояние формируется из начального значения input-элемента. Если поле уже содержит данные, библиотека немедленно применяет правила форматирования и приводит значение к согласованному виду.
const cleave = new Cleave(inputElement, {
numeral: true,
numeralThousandsGroupStyle: 'thousand'
});
В этом сценарии важно учитывать, что первичная инициализация уже изменяет DOM. Это означает, что состояние внешнего приложения должно быть либо синхронизировано заранее, либо считано после инициализации.
Для систем с контролируемыми компонентами это особенно критично: значение в state и значение в input должны совпасть до подключения Cleave.js, иначе возможны скачки курсора или переформатирование на первом рендере.
Основной механизм синхронизации — callback
onValueChanged. Он предоставляет доступ к текущему
состоянию поля в момент изменения.
const cleave = new Cleave(inputElement, {
phone: true,
onValueChanged: function (e) {
const { rawValue, value } = e.target;
appState.phoneRaw = rawValue;
appState.phoneFormatted = value;
}
});
Здесь происходит ключевое разделение ответственности:
Важно учитывать, что value — это уже форматированная
строка, а rawValue — нормализованное значение без маски.
Именно rawValue обычно используется в бизнес-логике.
В неконтролируемом режиме Cleave.js полностью управляет DOM-значением. Внешнее приложение не вмешивается в процесс ввода, а состояние извлекается только при необходимости, например при отправке формы.
const cleave = new Cleave(inputElement, {
numeral: true
});
form.addEventListener('submit', () => {
const value = cleave.getRawValue();
});
Такая модель снижает сложность синхронизации, но делает состояние «ленивым». Оно существует только в DOM до момента извлечения.
Недостаток подхода проявляется при необходимости реактивного поведения: изменение одного поля не влияет на другие части интерфейса без дополнительного ручного извлечения значения.
В средах с виртуальным DOM Cleave.js часто становится источником побочных эффектов, так как напрямую изменяет input. Поэтому управление состоянием требует строгой синхронизации.
Основная проблема — предотвращение конфликта между render-циклом и внутренним форматированием.
const [value, setValue] = useState('');
useEffect(() => {
const cleave = new Cleave(inputRef.current, {
numeral: true,
onValueChanged: (e) => {
setValue(e.target.rawValue);
}
});
return () => cleave.destroy();
}, []);
В этой модели:
Особенность заключается в том, что прямое обновление
value из state в input может конфликтовать с
форматированием Cleave.js, поэтому любые внешние изменения требуют
осторожности.
Cleave.js предоставляет методы для изменения значения без пользовательского ввода. Это важно для сценариев автозаполнения, восстановления формы и динамических вычислений.
cleave.setRawValue('79991234567');
После вызова метода библиотека автоматически пересчитывает отображаемое значение.
Однако программное обновление состояния может привести к рассинхронизации, если внешнее приложение не обновляет свой state одновременно. Поэтому правило заключается в том, что любое изменение через API Cleave.js должно сопровождаться обновлением внешнего состояния.
Состояние Cleave.js тесно связано с конфигурацией. Изменение опций (например, переход с телефонной маски на числовую или валютную) часто требует пересоздания экземпляра.
cleave.destroy();
const newCleave = new Cleave(inputElement, {
numeral: true,
numeralDecimalScale: 2
});
Причина в том, что внутренние алгоритмы форматирования зависят от набора правил, и частичное обновление может привести к неконсистентному состоянию.
При этом внешнее состояние должно быть сохранено отдельно:
Хотя курсор формально не является частью данных, в Cleave.js он становится элементом состояния, поскольку форматирование напрямую влияет на его позицию.
Каждое изменение значения может смещать caret, особенно при вставке разделителей или изменении длины строки.
Внутренне библиотека пересчитывает позицию курсора после каждого обновления, но при внешнем управлении состоянием возможны конфликты:
Поэтому важно избегать циклов «state → value → state», где каждое обновление инициирует новое форматирование.
В формах с динамическими полями (например, списки телефонов или банковских реквизитов) каждый экземпляр Cleave.js имеет собственное локальное состояние.
fields.forEach((field) => {
new Cleave(field.element, field.options);
});
В таких сценариях состояние приложения становится коллекцией независимых значений. Ключевая задача — обеспечить стабильную идентификацию каждого экземпляра, чтобы при удалении или добавлении поля не происходило смешивания данных.
Сброс состояния требует согласованного удаления как DOM-значения, так и внутреннего состояния Cleave.js.
cleave.setRawValue('');
или при полном сбросе:
cleave.destroy();
input.value = '';
Важно учитывать, что уничтожение экземпляра не очищает внешнее состояние приложения. Если state не синхронизирован, после повторной инициализации возможно восстановление старого значения.
Практика хранения formattedValue как основного состояния считается ошибочной в большинстве сценариев. Форматированное значение предназначено исключительно для отображения.
Корректная модель хранения выглядит следующим образом:
Такой подход упрощает валидацию, сериализацию и интеграцию с API, поскольку исключает зависимость от визуального представления данных.
Наиболее сложные проблемы возникают при асинхронных сценариях:
Если новое значение устанавливается до завершения инициализации Cleave.js, оно может быть переформатировано некорректно или частично проигнорировано.
Поэтому последовательность обновлений состояния критична:
Нарушение порядка приводит к дрейфу состояния между слоями приложения.
При проектировании систем с Cleave.js важно поддерживать несколько устойчивых правил:
Соблюдение этих инвариантов позволяет избежать рассинхронизации между пользовательским вводом и моделью данных приложения, особенно в сложных формах с динамическими изменениями и реактивными обновлениями.