Реализация модалки через шину событий
1. composable useModal.ts
// src/composables/useModal.ts
import { reactive, readonly, InjectionKey, inject, provide } from 'vue';
interface ModalState {
isOpen: boolean;
component: any;
props: Record<string, any>;
}
interface ModalMethods {
openModal: (component: any, props?: Record<string, any>) => void;
closeModal: () => void;
}
// Создаем уникальный ключ для provide/inject
const modalKey: InjectionKey<ReturnType<typeof createModal>> = Symbol('ModalKey');
// Функция для создания состояния и методов модальных окон
function createModal() {
const modalState = reactive<ModalState>({
isOpen: false,
component: null,
props: {},
});
const openModal = (component: any, props: Record<string, any> = {}) => {
modalState.isOpen = true;
modalState.component = component;
modalState.props = props;
};
const closeModal = () => {
modalState.isOpen = false;
modalState.component = null;
modalState.props = {};
};
return {
modalState: readonly(modalState),
openModal,
closeModal,
};
}
// Функция для предоставления модального состояния и методов
export function provideModal() {
const modal = createModal();
provide(modalKey, modal);
}
// Функция для использования модального состояния и методов в компонентах
export function useModal() {
const modal = inject(modalKey);
if (!modal) {
throw new Error('useModal() called without provider.');
}
return modal;
}
Пояснение:
- Мы создали функцию
createModal()
, которая инициализирует состояние и методы модальных окон. - В
provideModal()
мы создаем экземпляр модального состояния и предоставляем его черезprovide
. useModal()
используется для получения доступа к модальному состоянию и методам внутри компонентов.
2. App.vue
<!-- src/App.vue -->
<template>
<div id="app">
<!-- Ваше приложение -->
<ModalManager />
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import { provideModal } from './composables/useModal';
import ModalManager from './components/ModalManager.vue';
export default defineComponent({
name: 'App',
components: {
ModalManager,
},
setup() {
provideModal(); // Предоставляем модальное состояние и методы
},
});
</script>
Пояснение:
- Используем
setup
-функцию для вызоваprovideModal()
.
3. ModalManager.vue
<!-- src/components/ModalManager.vue -->
<template>
<component
v-if="modalState.isOpen"
:is="modalState.component"
v-bind="modalState.props"
@close="closeModal"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useModal } from '../composables/useModal';
export default defineComponent({
name: 'ModalManager',
setup() {
const { modalState, closeModal } = useModal();
return {
modalState,
closeModal,
};
},
});
</script>
Пояснение:
- Используем
setup
для получения модального состояния и методаcloseModal
черезuseModal()
.
4. Модальный компонент MyModal.vue
<!-- src/components/MyModal.vue -->
<template>
<div class="modal-overlay" @click.self="close">
<div class="modal-content">
<h2>{{ title }}</h2>
<p>{{ message }}</p>
<button @click="close">Закрыть</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
export default defineComponent({
name: 'MyModal',
props: {
title: {
type: String as PropType<string>,
required: true,
},
message: {
type: String as PropType<string>,
required: true,
},
},
emits: ['close'],
setup(props, { emit }) {
const close = () => {
emit('close');
};
return {
...props,
close,
};
},
});
</script>
<style scoped>
/* Стили модального окна */
.modal-overlay {
/* Ваши стили для затемненного фона */
}
.modal-content {
/* Ваши стили для содержимого модалки */
}
</style>
Пояснение:
- Используем
setup
для определения методов и доступа к пропсам.
5. Компонент, открывающий модалку
Теперь обновим компонент, который открывает модальное окно, чтобы можно было просто вызвать openMyModal()
без необходимости импортировать useModal
в каждом компоненте.
Чтобы этого достичь, мы можем экспортировать функцию openMyModal()
из composable и сделать ее доступной глобально.
Обновление useModal.ts
Добавим экспорт функции openMyModal
:
// Добавьте в конец файла useModal.ts
// Предоставляем глобальную функцию для открытия конкретной модалки
export function openMyModal() {
const modal = inject(modalKey);
if (!modal) {
throw new Error('openMyModal() called without provider.');
}
modal.openModal(MyModalComponent, {
title: 'Привет',
message: 'Это сообщение в модальном окне',
});
}
Однако, поскольку мы не можем использовать inject
вне setup
, нам нужно немного изменить подход.
Использование глобального экземпляра для доступа к модальному состоянию
Мы можем изменить useModal.ts
следующим образом:
// src/composables/useModal.ts
import { reactive, readonly } from 'vue';
interface ModalState {
isOpen: boolean;
component: any;
props: Record<string, any>;
}
const modalState = reactive<ModalState>({
isOpen: false,
component: null,
props: {},
});
const openModal = (component: any, props: Record<string, any> = {}) => {
modalState.isOpen = true;
modalState.component = component;
modalState.props = props;
};
const closeModal = () => {
modalState.isOpen = false;
modalState.component = null;
modalState.props = {};
};
export function useModal() {
return {
modalState: readonly(modalState),
openModal,
closeModal,
};
}
Теперь modalState
, openModal
, и closeModal
доступны напрямую из useModal()
, без необходимости provide
/inject
.
Обновим App.vue
:
<!-- src/App.vue -->
<template>
<div id="app">
<!-- Ваше приложение -->
<ModalManager />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ModalManager from './components/ModalManager.vue';
export default defineComponent({
name: 'App',
components: {
ModalManager,
},
setup() {
// Больше не нужно provideModal()
},
});
</script>
Обновим ModalManager.vue
:
<!-- src/components/ModalManager.vue -->
<template>
<component
v-if="modalState.isOpen"
:is="modalState.component"
v-bind="modalState.props"
@close="closeModal"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useModal } from '../composables/useModal';
export default defineComponent({
name: 'ModalManager',
setup() {
const { modalState, closeModal } = useModal();
return {
modalState,
closeModal,
};
},
});
</script>
Теперь мы можем создать функцию openMyModal
и экспортировать ее из useModal.ts
:
// src/composables/useModal.ts
import MyModal from '../components/MyModal.vue';
// ... предыдущий код
export function openMyModal() {
openModal(MyModal, {
title: 'Привет',
message: 'Это сообщение в модальном окне',
});
}
Теперь в любом компоненте мы можем импортировать openMyModal
и использовать ее без необходимости вызова useModal()
.
Пример компонента:
<!-- src/components/SomeComponent.vue -->
<template>
<button @click="openMyModal">Открыть модалку</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { openMyModal } from '../composables/useModal';
export default defineComponent({
name: 'SomeComponent',
setup() {
return {
openMyModal,
};
},
});
</script>
Пояснение:
- Мы экспортируем функцию
openMyModal
изuseModal.ts
. - В компонентах, где мы хотим открыть модальное окно, мы просто импортируем
openMyModal
и вызываем ее вsetup
.
Преимущества данного подхода
- Простота использования: В компонентах не нужно каждый раз использовать
useModal
; достаточно импортироватьopenMyModal
. - Чистая архитектура: Использование Composition API (
setup
) во всех компонентах и файлах обеспечивает современный и рекомендуемый подход в Vue 3. - Инкапсуляция: Управление модальными окнами скрыто внутри
useModal
, обеспечивая чистый код и легкую поддержку. - Поддержка TypeScript: Все компоненты и функции типизированы, что повышает надежность и удобство разработки.