Интеграция библиотеки Vue.js с AutoNumeric требует понимания жизненного цикла компонентов, реактивности, двустороннего связывания данных и особенностей работы с DOM. AutoNumeric напрямую управляет содержимым HTML-элемента, тогда как Vue использует собственную систему реактивного рендеринга. Из-за этого при неправильной интеграции возникают конфликты между виртуальным DOM и ручным изменением значения поля.
Наиболее частые проблемы:
v-model;npm install autonumeric
import AutoNumeric from 'autonumeric';
<template>
<input ref="priceInput" type="text">
</template>
<script>
import AutoNumeric from 'autonumeric';
export default {
mounted() {
this.anElement = new AutoNumeric(
this.$refs.priceInput,
{
currencySymbol: '₸ ',
decimalCharacter: ',',
digitGroupSeparator: ' '
}
);
},
beforeUnmount() {
if (this.anElement) {
this.anElement.remove();
}
}
};
</script>
Создание экземпляра AutoNumeric должно происходить только после появления DOM-элемента.
Правильный хук:
mounted()
Неправильно:
created()
beforeMount()
setup()
До выполнения mounted() ссылка ref ещё
отсутствует.
AutoNumeric регистрирует события:
inputkeydownfocusblurwheelЕсли не удалить экземпляр при уничтожении компонента, возникают:
Правильное удаление:
beforeUnmount() {
this.anElement.remove();
}
Vue ожидает обычное значение поля:
12345
AutoNumeric отображает форматированную строку:
12 345,00 ₸
Из-за этого прямое использование v-model приводит к
конфликтам.
<input v-model="price">
mounted() {
new AutoNumeric(this.$refs.input);
}
Vue пытается обновлять значение поля одновременно с AutoNumeric.
Последствия:
<template>
<input ref="priceInput" type="text">
</template>
<script>
import AutoNumeric from 'autonumeric';
export default {
data() {
return {
price: null
};
},
mounted() {
this.anElement = new AutoNumeric(this.$refs.priceInput);
this.$refs.priceInput.addEventListener('autoNumeric:rawValueModified', () => {
this.price = this.anElement.getNumber();
});
}
};
</script>
| Событие | Назначение |
|---|---|
autoNumeric:initialized |
экземпляр создан |
autoNumeric:formatted |
выполнено форматирование |
autoNumeric:rawValueModified |
изменилось raw-значение |
autoNumeric:minExceeded |
превышен минимум |
autoNumeric:maxExceeded |
превышен максимум |
autoNumeric:invalidValue |
введено некорректное значение |
this.$refs.priceInput.addEventListener(
'autoNumeric:rawValueModified',
(event) => {
console.log(event.detail.newRawValue);
}
);
<template>
<input ref="inputRef" type="text">
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import AutoNumeric from 'autonumeric';
const inputRef = ref(null);
let anElement = null;
onMounted(() => {
anElement = new AutoNumeric(inputRef.value, {
currencySymbol: '$ '
});
});
onBeforeUnmount(() => {
if (anElement) {
anElement.remove();
}
});
</script>
<script setup>
import { ref, onMounted } from 'vue';
import AutoNumeric from 'autonumeric';
const amount = ref(0);
const inputRef = ref(null);
let an = null;
onMounted(() => {
an = new AutoNumeric(inputRef.value);
inputRef.value.addEventListener(
'autoNumeric:rawValueModified',
() => {
amount.value = an.getNumber();
}
);
});
</script>
<template>
<input ref="inputRef" type="text">
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import AutoNumeric from 'autonumeric';
const model = defineModel();
const inputRef = ref(null);
let an = null;
onMounted(() => {
an = new AutoNumeric(inputRef.value);
inputRef.value.addEventListener(
'autoNumeric:rawValueModified',
() => {
model.value = an.getNumericString();
}
);
});
watch(model, (value) => {
if (an && value !== an.getNumericString()) {
an.set(value);
}
});
</script>
<template>
<input ref="inputRef" type="text">
</template>
<script>
import AutoNumeric from 'autonumeric';
export default {
props: {
modelValue: {
type: [String, Number],
default: ''
},
options: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue'],
mounted() {
this.anElement = new AutoNumeric(
this.$refs.inputRef,
this.modelValue,
this.options
);
this.$refs.inputRef.addEventListener(
'autoNumeric:rawValueModified',
() => {
this.$emit(
'update:modelValue',
this.anElement.getNumericString()
);
}
);
},
watch: {
modelValue(value) {
if (
this.anElement &&
value !== this.anElement.getNumericString()
) {
this.anElement.set(value);
}
}
},
beforeUnmount() {
this.anElement.remove();
}
};
</script>
<template>
<CurrencyInput
v-model="price"
:options="currencyOptions"
/>
</template>
<script>
import CurrencyInput from './CurrencyInput.vue';
export default {
components: {
CurrencyInput
},
data() {
return {
price: 15000,
currencyOptions: {
currencySymbol: '₸ ',
decimalCharacter: ',',
digitGroupSeparator: ' '
}
};
}
};
</script>
Vue может менять значение извне:
this.price = 100000;
AutoNumeric не узнает об изменении автоматически.
Необходим watch.
watch: {
price(newValue) {
this.anElement.set(newValue);
}
}
watch: {
price(newValue) {
this.anElement.set(newValue);
}
}
set() вызывает изменение значения.
Изменение значения вызывает событие.
Событие меняет price.
Получается цикл.
watch: {
price(newValue) {
const current = this.anElement.getNumericString();
if (newValue !== current) {
this.anElement.set(newValue);
}
}
}
computed: {
formattedPrice() {
return this.anElement
? this.anElement.getFormatted()
: '';
}
}
<template>
<div v-for="item in items" :key="item.id">
<input :ref="setInputRef">
</div>
</template>
<script>
import AutoNumeric from 'autonumeric';
export default {
data() {
return {
refs: [],
autoNumerics: []
};
},
methods: {
setInputRef(el) {
if (el) {
this.refs.push(el);
}
}
},
mounted() {
this.autoNumerics = this.refs.map((el) => {
return new AutoNumeric(el);
});
}
};
</script>
При перерисовке массива:
<div v-for="item in items" :key="item.id">
<CurrencyInput v-model="item.price" />
</div>
Каждый компонент управляет собственным экземпляром AutoNumeric.
watch: {
options: {
deep: true,
handler(newOptions) {
this.anElement.update(newOptions);
}
}
}
this.currencyOptions = {
currencySymbol: '$ '
};
provide() {
return {
autoNumericDefaults: {
digitGroupSeparator: ' ',
decimalCharacter: ','
}
};
}
inject: ['autoNumericDefaults']
import {
ref,
onMounted,
onBeforeUnmount
} from 'vue';
import AutoNumeric from 'autonumeric';
export function useAutoNumeric(options = {}) {
const inputRef = ref(null);
let an = null;
onMounted(() => {
an = new AutoNumeric(
inputRef.value,
options
);
});
onBeforeUnmount(() => {
if (an) {
an.remove();
}
});
return {
inputRef,
getInstance: () => an
};
}
<script setup>
import { useAutoNumeric } from './useAutoNumeric';
const {
inputRef,
getInstance
} = useAutoNumeric({
currencySymbol: '€ '
});
</script>
<template>
<input ref="inputRef">
</template>
AutoNumeric использует:
window
document
HTMLElement
На сервере этих объектов нет.
window is not defined
onMounted(async () => {
const AutoNumeric = (await import('autonumeric')).default;
new AutoNumeric(inputRef.value);
});
<ClientOnly>
<CurrencyInput />
</ClientOnly>
const AutoNumeric = await import('autonumeric');
Преимущества:
import AutoNumeric from 'autonumeric';
export default {
mounted(el, binding) {
el.anElement = new AutoNumeric(
el,
binding.value || {}
);
},
upd ated(el, binding) {
el.anElement.update(binding.value);
},
unmounted(el) {
el.anElement.remove();
}
};
app.directive('autonumeric', autonumericDirective);
<input
v-autonumeric="{
currencySymbol: '$ '
}"
>
Директива подходит для:
Недостатки:
v-model;export const usePaymentStore = defineStore('payment', {
state: () => ({
amount: 0
})
});
input.addEventListener(
'autoNumeric:rawValueModified',
() => {
store.amount = an.getNumber();
}
);
Неправильно:
"15 000,00 ₸"
Правильно:
15000
Форматирование должно быть задачей UI.
const value = an.getNumber();
value => value > 0
"10 000 ₽" > 0
Такое сравнение некорректно.
const amount = an.getNumericString();
const formatted = an.getFormatted();
| Метод | Результат |
|---|---|
getNumericString() |
"15000.5" |
getNumber() |
15000.5 |
getFormatted() |
"15 000,50 ₽" |
Vue обновляет DOM асинхронно.
AutoNumeric может обратиться к устаревшему элементу.
await nextTick();
anElement.se t(value);
При переносе элементов через:
<Teleport>
DOM-узел меняет положение.
Иногда требуется повторная инициализация.
activated() {
this.anElement.reformat();
}
AutoNumeric должен инициализироваться только после полной загрузки компонента.
Правильный хук:
onMounted()
import { mount } from '@vue/test-utils';
expect(wrapper.vm.price).toBe('15000');
expect(
input.element.value
).toContain('15 000');
import AutoNumeric from 'autonumeric';
let an: AutoNumeric | null = null;
interface Props {
modelValue: string | number;
}
1000+ полей
deep: true
an.update(...)
import debounce from 'lodash/debounce';
if (isVisible) {
new AutoNumeric(...)
}
const currencyOptions = Object.freeze({
currencySymbol: '$ '
});
<input v-model="price">
updated() {
new AutoNumeric(...)
}
beforeUnmount() {}
"10 000 ₽"
input.value = '100';
Необходимо использовать:
an.se t(100);
Наиболее стабильная архитектура:
v-model на input;<CurrencyInput
v-model="form.amount"
:options="currencyOptions"
/>
Внутри компонента: