Реализация модалки через шину событий
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: Все компоненты и функции типизированы, что повышает надежность и удобство разработки.