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

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

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

Основные задачи интеграции:

  • создание экземпляра Pikaday после появления элемента в DOM;
  • синхронизация выбранной даты с реактивным состоянием Vue;
  • обновление календаря при изменении данных приложения;
  • освобождение ресурсов при уничтожении компонента;
  • поддержка двустороннего связывания через v-model.

Установка зависимостей

Установка библиотеки выполняется через npm:

npm install pikaday

Подключение стилей:

import 'pikaday/css/pikaday.css';

Подключение самой библиотеки:

import Pikaday from 'pikaday';

Использование в компоненте Vue 3 Composition API

Наиболее распространённый вариант интеграции — создание экземпляра календаря внутри хука onMounted.

Базовый пример

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

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import Pikaday from 'pikaday';
import 'pikaday/css/pikaday.css';

const dateInput = ref(null);

let picker = null;

onMounted(() => {
  picker = new Pikaday({
    field: dateInput.value
  });
});

onBeforeUnmount(() => {
  picker.destroy();
});
</script>

В данном примере:

  • Vue создаёт элемент ввода;
  • после монтирования запускается Pikaday;
  • при уничтожении компонента календарь удаляется из памяти.

Сохранение выбранной даты в реактивном состоянии

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

<template>
  <input ref="dateInput" />
  <p>Дата: {{ selectedDate }}</p>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import Pikaday from 'pikaday';

const dateInput = ref(null);
const selectedDate = ref('');

let picker;

onMounted(() => {
  picker = new Pikaday({
    field: dateInput.value,

    onSelect(date) {
      selectedDate.value = date.toISOString();
    }
  });
});

onBeforeUnmount(() => {
  picker.destroy();
});
</script>

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


Форматирование даты

Часто требуется отображать дату в удобном формате.

onSelect(date) {
  selectedDate.value =
    date.toLocaleDateString('ru-RU');
}

Результат:

02.06.2026

Можно использовать сторонние библиотеки:

import dayjs from 'dayjs';

onSelect(date) {
  selectedDate.value =
    dayjs(date).format('DD.MM.YYYY');
}

Использование v-model

Для удобства интеграции часто создают собственный компонент-обёртку.

DatePicker.vue

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

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import Pikaday from 'pikaday';

const props = defineProps({
  modelValue: String
});

const emit = defineEmits([
  'update:modelValue'
]);

const input = ref(null);

let picker;

onMounted(() => {
  picker = new Pikaday({
    field: input.value,

    onSelect(date) {
      emit(
        'update:modelValue',
        date.toISOString()
      );
    }
  });
});

onBeforeUnmount(() => {
  picker.destroy();
});
</script>

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

<template>
  <DatePicker v-model="birthday" />
</template>

<script setup>
import { ref } from 'vue';
import DatePicker from './DatePicker.vue';

const birthday = ref('');
</script>

Такой подход делает компонент совместимым со стандартной системой двустороннего связывания Vue.


Синхронизация внешних изменений

Если значение изменяется не через календарь, а программно, Pikaday необходимо обновлять вручную.

<script setup>
import {
  ref,
  watch,
  onMounted
} from 'vue';

const model = ref('2026-06-02');

watch(model, value => {
  if (picker) {
    picker.setDate(value);
  }
});
</script>

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


Полная поддержка v-model

Более корректная реализация предусматривает двустороннюю синхронизацию.

<script setup>
import {
  ref,
  watch,
  onMounted,
  onBeforeUnmount
} from 'vue';

import Pikaday from 'pikaday';

const props = defineProps({
  modelValue: String
});

const emit = defineEmits([
  'update:modelValue'
]);

const input = ref();

let picker;

onMounted(() => {
  picker = new Pikaday({
    field: input.value,

    onSelect(date) {
      emit(
        'update:modelValue',
        date.toISOString()
      );
    }
  });

  if (props.modelValue) {
    picker.setDate(props.modelValue);
  }
});

watch(
  () => props.modelValue,
  value => {
    if (picker && value) {
      picker.setDate(value);
    }
  }
);

onBeforeUnmount(() => {
  picker.destroy();
});
</script>

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


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

Во многих проектах всё ещё применяется классический стиль компонентов Vue.

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

<script>
import Pikaday from 'pikaday';

export default {
  data() {
    return {
      picker: null
    };
  },

  mounted() {
    this.picker = new Pikaday({
      field: this.$refs.input
    });
  },

  beforeUnmount() {
    this.picker.destroy();
  }
};
</script>

Логика практически не отличается от Composition API.


Ограничение диапазона дат

Часто требуется разрешить выбор только определённого периода.

picker = new Pikaday({
  field: input.value,

  minDate: new Date(2025, 0, 1),

  maxDate: new Date(2026, 11, 31)
});

В Vue ограничения могут храниться в реактивном состоянии.

const minDate = ref(
  new Date(2025, 0, 1)
);

const maxDate = ref(
  new Date(2026, 11, 31)
);

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


Работа с несколькими календарями

Например, при выборе периода поездки.

<template>
  <input ref="startInput">
  <input ref="endInput">
</template>
let startPicker;
let endPicker;

onMounted(() => {
  startPicker = new Pikaday({
    field: startInput.value
  });

  endPicker = new Pikaday({
    field: endInput.value
  });
});

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


Связанные диапазоны дат

Начальная дата не должна быть больше конечной.

startPicker = new Pikaday({
  field: startInput.value,

  onSelect(date) {
    endPicker.setMinDate(date);
  }
});

endPicker = new Pikaday({
  field: endInput.value,

  onSelect(date) {
    startPicker.setMaxDate(date);
  }
});

Подобная логика часто используется в системах бронирования, отчётности и планирования.


Интеграция с Vuex и Pinia

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

Пример для Pinia

import { defineStore } from 'pinia';

export const useCalendarStore =
  defineStore('calendar', {
    state: () => ({
      selectedDate: null
    })
  });

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

const store = useCalendarStore();

picker = new Pikaday({
  field: input.value,

  onSelect(date) {
    store.selectedDate = date;
  }
});

Теперь дата доступна любому компоненту приложения.


Использование вычисляемых свойств

import { computed } from 'vue';

const formattedDate = computed(() => {
  if (!selectedDate.value) {
    return '';
  }

  return new Intl.DateTimeFormat(
    'ru-RU'
  ).format(
    new Date(selectedDate.value)
  );
});

В шаблоне:

<p>{{ formattedDate }}</p>

Подход позволяет отделить внутреннее хранение данных от их отображения.


Ленивое создание календаря

Если компонент отображается редко, можно отложить создание экземпляра до момента фокусировки поля.

const createPicker = () => {
  if (picker) return;

  picker = new Pikaday({
    field: input.value
  });
};
<input
  ref="input"
  @focus="createPicker"
/>

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


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

Vue позволяет перемещать части интерфейса в другие области DOM через Teleport.

При использовании Pikaday важно помнить, что библиотека самостоятельно вставляет календарь в документ и не зависит от механизма Teleport.

Поэтому календарь корректно работает внутри:

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

Основная задача заключается лишь в правильном создании и уничтожении экземпляра через жизненный цикл Vue.


Обработка событий Pikaday через Vue

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

onSelect(date) {
  emit('select', date);
}

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

<DatePicker
  @select="handleSelect"
/>
const handleSelect = date => {
  console.log(date);
};

Такой подход делает компонент более универсальным и переиспользуемым.


Очистка выбранной даты

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

picker.setDate(null);

Либо:

picker.setDate('');

С синхронизацией состояния Vue:

const clearDate = () => {
  picker.setDate(null);
  selectedDate.value = null;
};

Типичные проблемы интеграции

Календарь не отображается

Причины:

  • элемент ещё не создан в DOM;
  • экземпляр создаётся до onMounted;
  • отсутствуют CSS-стили Pikaday.

Возникают утечки памяти

Причина:

picker = new Pikaday(...);

без вызова:

picker.destroy();

в onBeforeUnmount.

Значение не обновляется в Vue

Причина обычно заключается в отсутствии обработчика:

onSelect(date) {
  selectedDate.value = date;
}

или отсутствии события:

emit(
  'update:modelValue',
  date
);

Дата обновляется дважды

Часто возникает при неправильной настройке watch и одновременном использовании setDate() внутри обработчика выбора даты. Для предотвращения циклических обновлений необходимо проверять текущее значение перед изменением.


Архитектура компонента-обёртки

Наиболее надёжная схема интеграции включает следующие элементы:

  1. Создание экземпляра Pikaday в onMounted.
  2. Уничтожение в onBeforeUnmount.
  3. Поддержку v-model.
  4. Синхронизацию через watch.
  5. Проброс пользовательских событий.
  6. Возможность передачи настроек через props.
  7. Изоляцию всей логики календаря внутри отдельного компонента.

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