Работа с Svelte

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

Svelte actions предоставляют наиболее естественный способ интеграции библиотек, работающих с DOM. AutoNumeric удобно инкапсулировать в action, который отвечает за инициализацию, обновление и уничтожение экземпляра.

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

import AutoNumeric from "autonumeric";

export function autoNumeric(node, options = {}) {
  const instance = new AutoNumeric(node, options);

  return {
    upd ate(newOptions) {
      instance.update(newOptions);
    },
    destroy() {
      instance.remove();
    }
  };
}

Использование в компоненте Svelte:

<script>
  import { autoNumeric } from "./autoNumericAction";

  let value = 12345.67;

  const options = {
    decimalPlaces: 2,
    digitGroupSeparator: " ",
    decimalCharacter: ","
  };
</script>

<input
  use:autoNumeric={options}
  bind:value
/>

Однако прямой bind:value в таком виде не синхронизирует состояние корректно, поскольку AutoNumeric перехватывает ввод и управляет значением через собственный API.

Корректная двусторонняя синхронизация

AutoNumeric предоставляет методы получения и установки числового значения без форматирования. В Svelte требуется вручную связать изменения DOM и реактивной переменной.

Расширенный вариант action:

import AutoNumeric from "autonumeric";

export function autoNumeric(node, options) {
  const instance = new AutoNumeric(node, options);

  function updateFromNode() {
    const numericValue = instance.getNumber();
    node.dispatchEvent(
      new CustomEvent("numeric-change", {
        detail: numericValue
      })
    );
  }

  node.addEventListener("input", updateFromNode);

  return {
    upd ate(newOptions) {
      instance.update(newOptions);
    },
    destroy() {
      node.removeEventListener("input", updateFromNode);
      instance.remove();
    }
  };
}

Использование с ручной синхронизацией:

<script>
  import { autoNumeric } from "./autoNumericAction";

  let value = 0;

  function handleChange(event) {
    value = event.detail;
  }
</script>

<input
  use:autoNumeric={{ decimalPlaces: 2 }}
  on:numeric-change={handleChange}
/>

<p>{value}</p>

Такой подход разделяет форматированное отображение и числовое состояние, что соответствует архитектуре Svelte.

Инициализация через bind:this и lifecycle

Другой способ интеграции заключается в использовании bind:this и жизненного цикла onMount, что дает полный контроль над экземпляром.

<script>
  import { onMount, onDestroy } from "svelte";
  import AutoNumeric from "autonumeric";

  let input;
  let auto;

  let value = 1000;

  onMount(() => {
    auto = new AutoNumeric(input, {
      decimalPlaces: 0,
      digitGroupSeparator: " "
    });

    auto.se t(value);

    input.addEventListener("input", sync);
  });

  function sync() {
    value = auto.getNumber();
  }

  $: if (auto && value !== auto.getNumber()) {
    auto.se t(value);
  }

  onDestroy(() => {
    input.removeEventListener("input", sync);
    auto.remove();
  });
</script>

<input bind:this={input} />

<p>{value}</p>

Здесь реактивное выражение $: обеспечивает синхронизацию внешних изменений value с состоянием AutoNumeric, предотвращая рассинхронизацию при программных обновлениях.

Управление форматированием через реактивные параметры

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

<script>
  import { onMount } from "svelte";
  import AutoNumeric from "autonumeric";

  let input;
  let auto;

  let precision = 2;
  let groupSeparator = " ";

  onMount(() => {
    auto = new AutoNumeric(input, {
      decimalPlaces: precision,
      digitGroupSeparator: groupSeparator
    });
  });

  $: if (auto) {
    auto.update({
      decimalPlaces: precision,
      digitGroupSeparator: groupSeparator
    });
  }
</script>

<input bind:this={input} />

Такой подход позволяет изменять формат без пересоздания экземпляра, что важно для производительности при частых обновлениях интерфейса.

Инкапсуляция в переиспользуемый компонент

Для крупных приложений рационально обернуть AutoNumeric в Svelte-компонент, скрывающий детали реализации.

<script>
  import { onMount, onDestroy } from "svelte";
  import AutoNumeric from "autonumeric";

  export let value = 0;
  export let options = {};

  let input;
  let instance;

  onMount(() => {
    instance = new AutoNumeric(input, options);
    instance.set(value);

    input.addEventListener("input", sync);
  });

  function sync() {
    value = instance.getNumber();
  }

  $: if (instance && value !== instance.getNumber()) {
    instance.set(value);
  }

  $: if (instance) {
    instance.update(options);
  }

  onDestroy(() => {
    input.removeEventListener("input", sync);
    instance.remove();
  });
</script>

<input bind:this={input} />

<style>
  input {
    width: 100%;
    padding: 6px 10px;
  }
</style>

Использование:

<NumericInput bind:value={price} options={{ decimalPlaces: 2 }} />

Особенности реактивности и предотвращение циклов

Главная сложность при работе AutoNumeric и Svelte заключается в двусторонней синхронизации, которая может приводить к циклам обновлений. Например, установка значения через instance.set() вызывает событие input, которое снова обновляет Svelte-переменную.

Для устранения этого применяется флаг блокировки:

<script>
  let locking = false;

  function sync() {
    if (locking) return;
    value = instance.getNumber();
  }

  $: if (instance) {
    locking = true;
    instance.set(value);
    locking = false;
  }
</script>

Альтернативный подход — сравнение значений перед установкой, что уменьшает количество лишних операций.

Работа с форматированными строками и числовыми значениями

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

  • getNumber() — возвращает чистое число
  • getFormatted() — возвращает строку с форматированием
  • set(value) — устанавливает значение
  • clear() — очищает поле

Использование строкового значения в реактивном состоянии приводит к рассинхронизации логики, поэтому модель данных должна оставаться числовой.

Динамическая смена локализации

При изменении региональных настроек требуется обновление нескольких параметров одновременно:

$: if (auto && locale === "ru") {
  auto.update({
    decimalCharacter: ",",
    digitGroupSeparator: " "
  });
}

$: if (auto && locale === "en") {
  auto.update({
    decimalCharacter: ".",
    digitGroupSeparator: ","
  });
}

Такой механизм позволяет переключать представление без пересоздания input-элемента и потери фокуса.

Использование с формами и валидацией

При интеграции в формы Svelte важно учитывать, что AutoNumeric изменяет отображаемое значение, но не управляет валидацией HTML5 напрямую. Валидация должна выполняться на уровне числового состояния:

<script>
  let value = 0;

  $: isValid = value >= 0 && value <= 10000;
</script>

<input use:autoNumeric on:numeric-change={(e) => value = e.detail} />

{#if !isValid}
  <span>Недопустимое значение</span>
{/if}

Такой подход разделяет ответственность: AutoNumeric отвечает за форматирование, Svelte — за бизнес-логику.

Оптимизация производительности

При частых обновлениях значений (например, в калькуляторах или финансовых формах) важно минимизировать вызовы upd ate() и set().

Рекомендуется:

  • избегать реактивных блоков без условий
  • сравнивать текущее и новое значение перед установкой
  • не пересоздавать экземпляр AutoNumeric без необходимости
  • использовать actions вместо глобальных слушателей

Эти принципы снижают количество перерасчётов DOM и предотвращают лаги при вводе больших чисел

Особенности SSR и гидратации

В SvelteKit или других SSR-сценариях AutoNumeric должен инициализироваться только на клиенте, поскольку он зависит от DOM.

<script>
  import { browser } from "$app/environment";
  import AutoNumeric from "autonumeric";

  let input;

  if (browser) {
    // безопасная инициализация
  }
</script>

Инициализация вне onMount приводит к ошибкам гидратации, так как DOM-элемент еще не существует.

Сложные сценарии с несколькими инстансами

В формах с несколькими числовыми полями часто требуется централизованное управление экземплярами AutoNumeric. Это достигается через коллекцию:

const instances = new Map();

export function register(node, options) {
  const instance = new AutoNumeric(node, options);
  instances.se t(node, instance);

  return instance;
}

export function destroy(node) {
  const instance = instances.get(node);
  if (instance) {
    instance.remove();
    instances.delete(node);
  }
}

Такой подход позволяет управлять группами полей, синхронизировать валютные настройки и выполнять массовые обновления конфигурации.