Переиспользование экземпляров

Экземпляр календаря Pikaday представляет собой объект, связанный с DOM-элементом input и внутренним состоянием выбора даты, рендеринга календарной сетки и обработчиков событий. При классическом использовании создаётся один экземпляр на один input:

const picker = new Pikaday({
    field: document.querySelector('#date'),
    format: 'YYYY-MM-DD'
});

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


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

Переиспользование заключается в том, что один и тот же объект Pikaday используется для нескольких input-полей или для одного поля, но с изменяющимися параметрами состояния (дата, минимальные/максимальные границы, позиционирование).

Ключевая особенность: Pikaday не «привязан навсегда» к одному input. Поле field можно переназначать, а состояние календаря — обновлять.


Переназначение поля input

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

const picker = new Pikaday({
    field: document.querySelector('#input-1'),
    format: 'YYYY-MM-DD'
});

// позже в коде
picker.config({
    field: document.querySelector('#input-2')
});

Однако в чистом Pikaday прямой метод config() не всегда используется для смены поля в зависимости от версии. Более надёжный подход — ручное отвязывание и привязка:

picker._o.field = document.querySelector('#input-2');
picker.hide();

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


Корректная стратегия: destroy и повторная привязка

В ситуациях, где DOM-узлы часто меняются, предпочтительно явное уничтожение экземпляра:

picker.destroy();

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

function createPicker(field) {
    return new Pikaday({
        field,
        format: 'YYYY-MM-DD'
    });
}

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


Переиспользование через скрытие и повторное открытие

Более эффективная модель в SPA — один глобальный экземпляр календаря, который «прикрепляется» к разным input при фокусе.

const picker = new Pikaday({
    field: document.createElement('input'),
    onSelect: function(date) {
        if (this._currentField) {
            this._currentField.value = this.toString();
        }
    }
});

Далее при фокусе:

document.addEventListener('focusin', (e) => {
    if (e.target.matches('.date-input')) {
        picker._currentField = e.target;
        picker._o.field = e.target;
        picker.setDate(e.target.value || new Date());
        picker.show();
    }
});

Такой паттерн позволяет:

  • избегать создания множества календарей
  • централизовать логику форматирования
  • уменьшить количество DOM-узлов календаря

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

При повторном использовании критически важно синхронизировать внутреннюю дату календаря с новым контекстом.

setDate как точка синхронизации

picker.setDate('2026-06-01', true);

Второй параметр true предотвращает триггер лишних событий, что важно при программной смене поля.

Очистка состояния

picker.setDate(null);

Это предотвращает «протекание» состояния между разными input.


gotoDate и логическая независимость UI

Метод gotoDate влияет только на отображаемый месяц, не меняя выбранную дату.

picker.gotoDate(new Date());

При переиспользовании это позволяет разъединить:

  • визуальное положение календаря
  • фактическое значение input

Это особенно полезно при переключении между полями с разными диапазонами дат.


Проблема утечек памяти при переиспользовании

Основной риск — неочищенные обработчики событий.

Pikaday внутри создаёт:

  • listeners на document
  • события на input
  • внутренние замыкания на конфигурацию

Если экземпляр просто «перекидывать» между полями без очистки старых связей, возникают:

  • дублирование обработчиков
  • некорректное срабатывание onSelect
  • рост памяти

Признак проблемы

Календарь начинает вызывать onSelect несколько раз за одно действие.


Паттерн singleton для Pikaday

В сложных интерфейсах часто применяется единый экземпляр:

const DatePickerSingleton = (function () {
    let instance;

    function create() {
        return new Pikaday({
            field: document.createElement('input')
        });
    }

    return {
        getInstance() {
            if (!instance) {
                instance = create();
            }
            return instance;
        }
    };
})();

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

const picker = DatePickerSingleton.getInstance();

Далее логика переключения поля управляется отдельно.


Переиспользование в динамических формах

В формах, где поля создаются и удаляются динамически, переиспользование снижает накладные расходы.

Пример структуры:

function attachDateInputs(container) {
    const picker = window.sharedPicker;

    container.querySelectorAll('.date-input').forEach(input => {
        input.addEventListener('focus', () => {
            picker._o.field = input;
            picker.setDate(input.value || new Date(), true);
            picker.show();
        });
    });
}

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


Переиспользование в SPA-архитектуре

В SPA (React, Vue, Angular) ключевая проблема — повторный рендер компонентов.

Ошибка частого паттерна

useEffect(() => {
    const picker = new Pikaday({ field: ref.current });

    return () => picker.destroy();
}, []);

При этом при каждом монтировании создаётся новый экземпляр.

Оптимизированный подход

Использование глобального экземпляра:

  • один Pikaday на приложение
  • управление через события компонентов
  • отсутствие постоянного destroy/create цикла

Переключение между режимами работы

Переиспользование часто включает изменение конфигурации:

picker.setMinDate(new Date('2026-01-01'));
picker.setMaxDate(new Date('2026-12-31'));

Хотя Pikaday не всегда предоставляет явные методы для всех параметров, многие ограничения можно менять через внутренний объект конфигурации:

picker._o.minDate = new Date('2026-01-01');
picker._o.maxDate = new Date('2026-12-31');
picker.draw();

Скрытие вместо уничтожения

Метод hide() является ключевым элементом переиспользования:

picker.hide();

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

Это важно для:

  • модальных форм
  • табличных интерфейсов
  • inline-редакторов

Комбинированный паттерн управления

Наиболее устойчивый подход объединяет несколько стратегий:

  • один глобальный экземпляр
  • динамическая смена field
  • явная синхронизация через setDate
  • скрытие вместо уничтожения
  • редкая полная пересборка через destroy
function bindPickerToField(picker, field) {
    picker._o.field = field;
    picker.setDate(field.value || null, true);
    picker.hide();
}

Особенности работы с inline режимом

При container-режиме переиспользование усложняется, так как календарь встроен в DOM:

new Pikaday({
    field: input,
    bound: false,
    container: document.querySelector('#calendar')
});

Здесь переиспользование чаще сводится к:

  • перемещению контейнера
  • пересозданию календарной сетки через draw()
  • обновлению field

Синхронизация событий при переиспользовании

Особое внимание требуется обработчику onSelect:

onSelect: function(date) {
    if (this._o.field) {
        this._o.field.value = this.toString();
    }
}

При переиспользовании важно, чтобы this._o.field всегда указывал на актуальный input, иначе данные будут записываться в устаревший элемент.


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

При правильной организации один экземпляр Pikaday перестаёт быть «виджетом для input» и становится централизованным сервисом выбора даты:

  • управляет отображением
  • хранит текущее состояние
  • обслуживает множество источников ввода
  • минимизирует создание DOM и слушателей событий

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