Модалки
1. Простой компонент модального окна
Описание:
Создайте компонент модального окна (Modal.vue
) и используйте его в местах, где нужна модалка. Управление видимостью осуществляется с помощью локального состояния и директивы v-if
.
Код:
Modal.vue
<template>
<div class="modal-overlay" @click.self="close">
<div class="modal-content">
<slot></slot>
<button @click="close">Закрыть</button>
</div>
</div>
</template>
<script>
export default {
methods: {
close() {
this.$emit('close');
}
}
}
</script>
<style scoped>
/* Стили модального окна */
.modal-overlay {
/* стили для затемнения фона */
}
.modal-content {
/* стили для контента модалки */
}
</style>
Родительский компонент:
<template>
<div>
<button @click="isModalVisible = true">Открыть модалку</button>
<Modal v-if="isModalVisible" @close="isModalVisible = false">
<p>Содержимое модального окна</p>
</Modal>
</div>
</template>
<script>
import Modal from './Modal.vue';
export default {
components: { Modal },
data() {
return {
isModalVisible: false
};
}
}
</script>
Плюсы:
- Простота реализации: легко понять и настроить.
- Локальное управление: состояние модалки управляется внутри компонента.
Минусы:
- Ограниченность использования: сложно использовать одну и ту же модалку в разных местах без дублирования кода.
- Глубокая вложенность: при глубокой иерархии компонентов передача событий может стать сложной.
2. Использование Teleport (доступно в Vue 3)
Описание:
Teleport позволяет рендерить компонент вне текущей иерархии DOM. Это удобно для модалок, которые должны быть размещены непосредственно внутри <body>
.
Код:
Modal.vue
<template>
<Teleport to="body">
<div class="modal-overlay" v-if="visible" @click.self="close">
<div class="modal-content">
<slot></slot>
<button @click="close">Закрыть</button>
</div>
</div>
</Teleport>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
required: true
}
},
methods: {
close() {
this.$emit('close');
}
}
}
</script>
Родительский компонент:
<template>
<div>
<button @click="isModalVisible = true">Открыть модалку</button>
<Modal :visible="isModalVisible" @close="isModalVisible = false">
<p>Содержимое модального окна</p>
</Modal>
</div>
</template>
<script>
import Modal from './Modal.vue';
export default {
components: { Modal },
data() {
return {
isModalVisible: false
};
}
}
</script>
Плюсы:
- Отсутствие проблем с вложенностью: модалка рендерится непосредственно внутри
<body>
. - Глобальный доступ к стилям: стили применяются корректно вне зависимости от местоположения компонента.
Минусы:
- Требуется Vue 3: недоступно в более ранних версиях.
- Потенциальные сложности с SSR: может потребоваться дополнительная настройка для серверного рендеринга.
3. Композиционный подход с provide/inject
// composables/useModal.ts
import { provide, inject, ref } from 'vue';
const ModalSymbol = Symbol();
export function provideModal() {
const isOpen = ref(false);
const modalComponent = ref(null);
const modalProps = ref({});
const openModal = (component: any, props = {}) => {
modalComponent.value = component;
modalProps.value = props;
isOpen.value = true;
};
const closeModal = () => {
isOpen.value = false;
modalComponent.value = null;
modalProps.value = {};
};
provide(ModalSymbol, {
isOpen,
modalComponent,
modalProps,
openModal,
closeModal
});
return {
openModal,
closeModal
};
}
export function useModal() {
const modal = inject(ModalSymbol);
if (!modal) throw new Error('Modal context not found!');
return modal;
}
<!-- ModalProvider.vue -->
<template>
<div class="modal-provider">
<slot></slot>
<Teleport to="body">
<Transition name="fade">
<div v-if="isOpen" class="modal-overlay">
<component
:is="modalComponent"
v-bind="modalProps"
@close="closeModal"
/>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { provideModal } from './composables/useModal';
const { isOpen, modalComponent, modalProps, closeModal } = provideModal();
</script>
Плюсы:
- Чистая композиционная логика.
- Гибкое управление состоянием.
- Хорошая инкапсуляция.
Минусы:
- Может быть сложным для понимания новичками
- Требует правильной структуры приложения
- Необходимость оборачивать приложение в провайдер
4. Использование глобального события (Event Bus)
Описание:
Создайте глобальный шиной событий, который позволит компонентам общаться друг с другом без прямых отношений родитель-ребёнок. Это позволит открывать и закрывать модальные окна из любого места приложения.
Реализация:
Создайте Event Bus
Создайте файл
eventBus.js
:javascriptimport { reactive } from 'vue'; const eventBus = reactive({}); export default eventBus;
Реализуйте менеджер модальных окон
Создайте компонент
ModalManager.vue
, который будет слушать события и отображать соответствующее модальное окно.ModalManager.vue
vue<template> <component v-if="modalComponent" :is="modalComponent" v-bind="modalProps" @close="closeModal" ></component> </template> <script> import { reactive, onMounted } from 'vue'; import eventBus from '../eventBus'; export default { name: 'ModalManager', setup() { const state = reactive({ modalComponent: null, modalProps: {}, }); const openModal = (component, props = {}) => { state.modalComponent = component; state.modalProps = props; }; const closeModal = () => { state.modalComponent = null; state.modalProps = {}; }; onMounted(() => { eventBus.openModal = openModal; eventBus.closeModal = closeModal; }); return { modalComponent: state.modalComponent, modalProps: state.modalProps, closeModal, }; }, }; </script>
Добавьте
ModalManager
в ваше приложениеВключите
ModalManager
вApp.vue
или другой корневой компонент.App.vue
vue<template> <div id="app"> <!-- Ваше приложение --> <ModalManager /> </div> </template> <script> import ModalManager from './components/ModalManager.vue'; export default { components: { ModalManager, }, }; </script>
Создайте модальные компоненты
MyModal.vue
vue<template> <div class="modal-overlay" @click.self="$emit('close')"> <div class="modal-content"> <h2>{{ title }}</h2> <p>{{ message }}</p> <button @click="$emit('close')">Закрыть</button> </div> </div> </template> <script> export default { props: { title: String, message: String, }, }; </script>
Открывайте модалки из любого компонента
vue<template> <button @click="openMyModal">Открыть модалку</button> </template> <script> import eventBus from '../eventBus'; import MyModal from './MyModal.vue'; export default { methods: { openMyModal() { eventBus.openModal(MyModal, { title: 'Привет', message: 'Это сообщение в модальном окне', }); }, }, }; </script>
Плюсы:
- Централизованное управление: Все модальные окна управляются из одного места.
- Простота доступа: Модалки можно открывать и закрывать из любого компонента.
- Без использования store: Нет необходимости в дополнительных зависимостях.
Минусы:
- Потенциальный беспорядок в глобальном пространстве: Глобальное использование eventBus может привести к трудностям в отладке.
- Отсутствие явных зависимостей: Труднее отслеживать, какие компоненты зависят от модального менеджера.
5. Использование Provide/Inject
Описание:
Используйте механизм provide
и inject
в Vue для передачи методов открытия и закрытия модалок вниз по дереву компонентов без необходимости пропс-дерриллинга.
Реализация:
Предоставьте методы модального управления
В корневом компоненте (например,
App.vue
):vue<template> <div id="app"> <!-- Ваше приложение --> <ModalManager /> </div> </template> <script> import { provide, reactive } from 'vue'; import ModalManager from './components/ModalManager.vue'; export default { components: { ModalManager }, setup() { const state = reactive({ modalComponent: null, modalProps: {}, }); const openModal = (component, props = {}) => { state.modalComponent = component; state.modalProps = props; }; const closeModal = () => { state.modalComponent = null; state.modalProps = {}; }; provide('modalState', state); provide('openModal', openModal); provide('closeModal', closeModal); }, }; </script>
Модальный менеджер использует Inject
ModalManager.vue
vue<template> <component v-if="modalState.modalComponent" :is="modalState.modalComponent" v-bind="modalState.modalProps" @close="closeModal" ></component> </template> <script> import { inject } from 'vue'; export default { name: 'ModalManager', setup() { const modalState = inject('modalState'); const closeModal = inject('closeModal'); return { modalState, closeModal, }; }, }; </script>
Компоненты используют Inject для открытия модалок
vue<template> <button @click="openMyModal">Открыть модалку</button> </template> <script> import { inject } from 'vue'; import MyModal from './MyModal.vue'; export default { setup() { const openModal = inject('openModal'); const openMyModal = () => { openModal(MyModal, { title: 'Привет', message: 'Это сообщение в модальном окне', }); }; return { openMyModal, }; }, }; </script>
Плюсы:
- Избегает глобального пространства: Нет необходимости в глобальных переменных.
- Явные зависимости: Компоненты явно указывают на свои зависимости через
inject
.
Минусы:
- Ограничения по глубине вложенности: Может быть сложно при очень глубокой или динамической иерархии компонентов.
- Может быть менее понятным для новичков: Требует понимания механизма
provide
/inject
.
6. Использование реактивных глобальных переменных
Описание:
Создайте реактивный объект состояния модальных окон и экспортируйте его. Компоненты могут импортировать функции для управления модалками и сам объект состояния для отображения.
Реализация:
Создайте глобальное состояние модалок
modalState.js
javascriptimport { reactive } from 'vue'; export const modalState = reactive({ component: null, props: {}, }); export const openModal = (component, props = {}) => { modalState.component = component; modalState.props = props; }; export const closeModal = () => { modalState.component = null; modalState.props = {}; };
Модальный менеджер использует состояние
ModalManager.vue
vue<template> <component v-if="modalState.component" :is="modalState.component" v-bind="modalState.props" @close="closeModal" ></component> </template> <script> import { modalState, closeModal } from '../modalState'; export default { setup() { return { modalState, closeModal, }; }, }; </script>
Компоненты управляют модалками через импортированные функции
vue<template> <button @click="openMyModal">Открыть модалку</button> </template> <script> import { openModal } from '../modalState'; import MyModal from './MyModal.vue'; export default { methods: { openMyModal() { openModal(MyModal, { title: 'Привет', message: 'Это сообщение в модальном окне', }); }, }, }; </script>
Плюсы:
- Простота: Нет необходимости в сложных настройках или плагинах.
- Явные зависимости через импорт: Легко увидеть, откуда приходят функции.
Минусы:
- Глобальное состояние: Может привести к трудностям в поддержке и отладке.
- Риск дублирования состояния: Если неаккуратно импортировать, можно создать несколько экземпляров состояния.
Общий вывод
- Простой компонент подходит для небольших проектов с ограниченным использованием модалок.
- Teleport идеален, когда требуется избежать проблем с вложенностью и обеспечить корректное отображение модалки на уровне
<body>
.
Выбор метода зависит от:
- Размеров и сложности вашего проекта
- Требований к функциональности модального окна
- Необходимости в кастомизации и контроле
- Предпочтений по управлению состоянием приложения