При использовании библиотеки Pikaday в приложениях на Vue возникает необходимость согласовать два разных подхода к работе с интерфейсом. Vue управляет DOM через реактивную систему и виртуальное дерево компонентов, тогда как Pikaday напрямую взаимодействует с элементами страницы и самостоятельно изменяет их состояние.
Из-за этого интеграция требует правильной организации жизненного цикла компонента, синхронизации данных и корректного уничтожения экземпляра календаря при размонтировании компонента.
Основные задачи интеграции:
v-model.Установка библиотеки выполняется через npm:
npm install pikaday
Подключение стилей:
import 'pikaday/css/pikaday.css';
Подключение самой библиотеки:
import Pikaday from 'pikaday';
Наиболее распространённый вариант интеграции — создание экземпляра
календаря внутри хука 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>
В данном примере:
Обычно дата должна храниться внутри реактивной переменной.
<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');
}
Для удобства интеграции часто создают собственный компонент-обёртку.
<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>
Теперь календарь автоматически реагирует на изменение данных приложения.
Более корректная реализация предусматривает двустороннюю синхронизацию.
<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>
Такой компонент одинаково хорошо работает как при выборе даты пользователем, так и при программном обновлении значения.
Во многих проектах всё ещё применяется классический стиль компонентов 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);
}
});
Подобная логика часто используется в системах бронирования, отчётности и планирования.
В крупных приложениях выбранная дата обычно хранится в глобальном хранилище.
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"
/>
Это уменьшает количество одновременно работающих экземпляров в больших интерфейсах.
Vue позволяет перемещать части интерфейса в другие области DOM через
Teleport.
При использовании Pikaday важно помнить, что библиотека самостоятельно вставляет календарь в документ и не зависит от механизма Teleport.
Поэтому календарь корректно работает внутри:
Основная задача заключается лишь в правильном создании и уничтожении экземпляра через жизненный цикл 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;
};
Причины:
onMounted;Причина:
picker = new Pikaday(...);
без вызова:
picker.destroy();
в onBeforeUnmount.
Причина обычно заключается в отсутствии обработчика:
onSelect(date) {
selectedDate.value = date;
}
или отсутствии события:
emit(
'update:modelValue',
date
);
Часто возникает при неправильной настройке watch и
одновременном использовании setDate() внутри обработчика
выбора даты. Для предотвращения циклических обновлений необходимо
проверять текущее значение перед изменением.
Наиболее надёжная схема интеграции включает следующие элементы:
onMounted.onBeforeUnmount.v-model.watch.Такой подход позволяет использовать Pikaday в проектах на Vue так же удобно, как любой нативный компонент экосистемы, сохраняя реактивность, предсказуемость поведения и совместимость с архитектурными принципами современного Vue-приложения.