Интеграция со сторонними библиотеками

Интеграция AutoNumeric со сторонними библиотеками часто требует учета того, как различные инструменты управления формами, компонентные фреймворки и утилиты работы с DOM обрабатывают ввод числовых значений, синхронизацию состояния и форматирование отображаемых данных.

AutoNumeric работает напрямую с DOM-элементами input, модифицируя отображаемое значение и одновременно сохраняя числовую модель. При подключении к сторонним библиотекам важно учитывать два слоя данных:

  • Отображаемое значение — форматированное число (разделители тысяч, валюты, точность).
  • Реальное значение — чистое числовое представление без форматирования.

Ключевой момент интеграции заключается в предотвращении конфликтов между:

  • контролем значения со стороны AutoNumeric;
  • контролем состояния со стороны фреймворка или библиотеки форм.

Интеграция с React

React управляет состоянием через controlled components, что может конфликтовать с прямым DOM-манипулированием AutoNumeric. Поэтому основной подход — изоляция DOM-узла через ref.

import React, { useEffect, useRef } from "react";
import AutoNumeric from "autonumeric";

export default function NumericInput({ value, onChange }) {
  const inputRef = useRef(null);
  const anInstance = useRef(null);

  useEffect(() => {
    if (inputRef.current) {
      anInstance.current = new AutoNumeric(inputRef.current, value, {
        decimalPlaces: 2,
        digitGroupSeparator: " ",
        decimalCharacter: ".",
      });

      anInstance.current.set(value);
    }

    return () => {
      if (anInstance.current) {
        anInstance.current.remove();
      }
    };
  }, []);

  useEffect(() => {
    if (anInstance.current) {
      anInstance.current.set(value);
    }
  }, [value]);

  const handleChange = () => {
    if (anInstance.current) {
      onChange(anInstance.current.getNumber());
    }
  };

  return <input ref={inputRef} onIn put={handleChange} />;
}

Ключевые моменты интеграции с React:

  • использование ref вместо controlled value;
  • обновление состояния через onInput, а не onChange;
  • синхронизация внешнего состояния через set().

Интеграция с React Hook Form

При работе с React Hook Form важно учитывать, что библиотека ожидает “чистые” значения, тогда как AutoNumeric оперирует форматированными строками.

import { useForm, Controller } from "react-hook-form";
import AutoNumeric from "autonumeric";
import React, { useRef, useEffect } from "react";

function NumericField({ control, name }) {
  const inputRef = useRef(null);
  const anRef = useRef(null);

  useEffect(() => {
    if (inputRef.current) {
      anRef.current = new AutoNumeric(inputRef.current, null, {
        decimalPlaces: 2,
      });
    }

    return () => anRef.current?.remove();
  }, []);

  return (
    <Controller
      control={control}
      name={name}
      render={({ field }) => (
        <input
          ref={inputRef}
          onIn put={() => field.onChange(anRef.current.getNumber())}
          defaultValue={field.value}
        />
      )}
    />
  );
}

Особенности:

  • Controller используется вместо register;
  • значение берется через getNumber();
  • AutoNumeric не хранит состояние формы — только отображение.

Интеграция с Vue

Vue 3 использует реактивную модель, поэтому интеграция строится через onMounted и watch.

<template>
  <input ref="inputRef" />
</template>

<script>
import AutoNumeric from "autonumeric";
import { ref, onMounted, watch } from "vue";

export default {
  props: ["modelValue"],
  emits: ["update:modelValue"],

  setup(props, { emit }) {
    const inputRef = ref(null);
    let anInstance = null;

    onMounted(() => {
      anInstance = new AutoNumeric(inputRef.value, props.modelValue, {
        decimalPlaces: 2,
      });

      inputRef.value.addEventListener("input", () => {
        emit("update:modelValue", anInstance.getNumber());
      });
    });

    watch(
      () => props.modelValue,
      (val) => {
        if (anInstance) {
          anInstance.set(val);
        }
      }
    );

    return { inputRef };
  },
};
</script>

Основные принципы:

  • двусторонняя синхронизация через v-model;
  • обновление через watch;
  • избегание прямого изменения value input.

Интеграция с Angular

Angular требует работы через ElementRef и жизненный цикл директив.

import { Directive, ElementRef, Input, OnInit, OnDestroy } from "@angular/core";
import AutoNumeric from "autonumeric";

@Directive({
  selector: "[appNumeric]",
})
export class NumericDirective implements OnInit, OnDestroy {
  @Input() options: any;
  private anInstance: any;

  constructor(private el: ElementRef) {}

  ngOnInit() {
    this.anInstance = new AutoNumeric(this.el.nativeElement, null, {
      decimalPlaces: 2,
      ...this.options,
    });

    this.el.nativeElement.addEventListener("input", () => {
      const value = this.anInstance.getNumber();
      this.el.nativeElement.dispatchEvent(
        new CustomEvent("numericChange", { detail: value })
      );
    });
  }

  ngOnDestroy() {
    this.anInstance?.remove();
  }
}

Особенности Angular-интеграции:

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

Интеграция с Axios и AJAX-отправкой данных

AutoNumeric не должен участвовать в передаче данных на сервер, так как сервер ожидает “сырое” число.

import axios from "axios";

function submitForm(anInstance) {
  const payload = {
    amount: anInstance.getNumber(),
  };

  return axios.post("/api/payment", payload);
}

Критически важно:

  • никогда не отправлять getFormatted();
  • использовать только getNumber().

Интеграция с библиотеками валидации (Yup, Joi, Zod)

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

import * as yup from "yup";

const schema = yup.object({
  amount: yup
    .number()
    .typeError("Должно быть числом")
    .min(0, "Минимум 0")
    .required(),
});

В связке с AutoNumeric:

  • валидируется результат getNumber();
  • форматирование не влияет на схему.

Интеграция с масками ввода

При совместном использовании с input-mask библиотеками возникает конфликт управления input.value. Поэтому применяется правило приоритета:

  • AutoNumeric управляет числовым форматированием;
  • маски применяются только к нефинансовым полям;
  • одновременное использование требует отключения пересекающихся функций.

Интеграция с i18n (локализация чисел)

AutoNumeric может быть настроен динамически в зависимости от локали приложения.

function createNumeric(input, locale) {
  const config = {
    decimalCharacter: locale === "de-DE" ? "," : ".",
    digitGroupSeparator: locale === "de-DE" ? "." : ",",
  };

  return new AutoNumeric(input, null, config);
}

При смене языка:

  • экземпляр пересоздается;
  • или обновляется через setOptions().

Интеграция с state management (Redux, Pinia, Vuex)

Состояние хранилища всегда должно содержать числовое значение.

dispatch({
  type: "SET_AMOUNT",
  payload: anInstance.getNumber(),
});

Ошибочная практика:

  • хранение строк с форматированием;
  • сохранение UI-значений вместо модели данных.

Комбинирование с кастомными компонентами UI библиотек

При использовании Material UI, Ant Design или аналогов необходимо учитывать, что многие компоненты проксируют input.

Пример подхода:

  • доступ к внутреннему input через inputRef;
  • инициализация AutoNumeric только после mount;
  • отключение встроенного форматирования компонента.

Частые проблемы интеграции

Конфликт controlled/uncontrolled состояния

  • причина: одновременно React/Vue и AutoNumeric управляют value;
  • решение: делегирование управления AutoNumeric через ref.

Прыгающий курсор

  • причина: перезапись value при каждом вводе;
  • решение: обновление только при внешних изменениях.

Несоответствие типов

  • причина: смешивание string и number;
  • решение: единый контракт getNumber().

Дублирование событий

  • причина: слушатели на input + framework binding;
  • решение: централизованный обработчик изменений.

Архитектурный паттерн интеграции

На практике наиболее устойчивой является модель разделения:

  • UI слой (AutoNumeric) — форматирование и ввод;
  • Domain слой — числовые данные;
  • Transport слой — сериализация и отправка;
  • State слой — хранение чистых значений.

Такое разделение исключает зависимость бизнес-логики от представления и снижает вероятность рассинхронизации данных при масштабировании приложения.