В этой статье я расскажу вам, какие библиотеки компонентов, такие как PrimeVue и Vuetify, предлагают для работы с диалогами. Тогда я скажу вам, что я думаю, что это лучший способ сделать это. Наконец, я покажу вам, как использовать vue3-promise-dialog.
Диалоги как обычно
Большинство библиотек компонентов предлагают компонент Dialog.
Это пример от PrimeVue:
Как видите, библиотеки компонентов поощряют встраивание кода диалога (шаблона и логики) прямо в родительский компонент, открывающий его. У этого есть несколько минусов:
- Это делает невозможным создание многоразовых диалогов, поскольку каждый диалог привязан к своему открывающемуся сайту.
- Логика и шаблон диалога смешаны с логикой и шаблоном его родительского компонента. Это становится еще более беспорядочным, когда родительский компонент должен открывать несколько диалогов.
- Невозможно открыть диалог из файла JS/TS.
- Диалог — это просто способ получения данных от пользователя, его открытие и ожидание результата должно быть таким же простым, как вызов асинхронной функции, точно так же, как получение данных с сервера. Как это делают библиотеки компонентов, это не так.
Диалоги с обещаниями
Как мы все знаем, запрос данных с сервера — это асинхронный процесс, который лучше всего обрабатывается с помощью промисов. Например, Fetch API — это API на основе обещаний.
let response = await fetch('http://example.com/movies.json');
Теперь, как и при получении данных с сервера, получение данных от пользователя с помощью диалогового окна также является асинхронным процессом, который может завершиться со значением в какой-то момент в будущем, когда пользователь закроет диалоговое окно. Почему API для получения данных от пользователя отличается от API для получения данных с сервера?
Используя промисы, открыть диалоговое окно подтверждения (например) так же просто, как это:
let ok = await confirm('Are you sure you want to do this ?'); if (ok) { // Do something }
Обещание преобразуется в значение, которое пользователь ввел в диалоговое окно (логическое значение в случае диалогового окна подтверждения, но это может быть любая структура данных, если диалоговое окно включает, например, форму) при его закрытии.
Такой подход имеет несколько преимуществ:
- Диалог не привязан к сайту открытия, поэтому легко создавать повторно используемые диалоги.
- Диалог можно открыть из родительского компонента Vue или из любого файла JS/TS. Это просто вызов функции.
- Существует приятная симметрия между запросом данных от пользователя и с сервера.
- Код диалога и код родительского компонента хранятся отдельно и независимо. Таким образом, вам нужно решить две отдельные задачи, что проще, чем решать их смесь.
- Родительский компонент может использовать множество диалогов, не создавая беспорядка.
Это все хорошо на бумаге, но как вы должны реализовать это? Для Vue 2 вы можете использовать vue-modal-dialog. Для Vue 3 вы можете использовать vue3-promise-dialog. Я покажу вам, как использовать последний в следующем разделе.
Обещайте свои диалоги с помощью vue3-promise-dialog
Я покажу вам, как создать базовый диалог подтверждения. Это не будет красиво или что-то в этом роде, но это будет функционально. Вы можете поиграть с полным проектом на StackBlitz: Vue 3 + Vite + vue3-promise-dialog. Если вам нужен пример чего-то красивого, ознакомьтесь с небольшой коллекцией диалогов, которая служит тестовым примером для библиотеки и ее демо здесь.
Вот что мы собираемся построить:
Сначала установите библиотеку:
npm i vue3-promise-dialog
Затем установите плагин:
import { createApp } from 'vue' import App from './App.vue' import {PromiseDialog} from "vue3-promise-dialog" const app = createApp(App); app.use(PromiseDialog); app.mount('#app')
Затем добавьте DialogWrapper
component в конце корневого компонента вашего приложения:
<template> <div id="app"> <!-- your content --> <DialogWrapper :transition-attrs="{name: 'dialog'}"/> </div> </template>
Ваши диалоги будут открываться внутри этого DialogWrapper
. Внутренне DialogWrapper
использует тег перехода для перехода ваших диалогов в поле зрения и из него. В моем примере выше имя перехода dialog
. Используйте классы dialog-enter-from
, dialog-leave-to
и т. д., чтобы анимировать появление и исчезновение ваших диалогов. Мы не будем делать ничего из этого здесь, чтобы все было просто.
Теперь вот код нашего диалога подтверждения:
Как видите, это простой компонент Vue. Это темный фон и центрированный div с кнопками YES и NO. У него есть реквизит text
для управления вопросом, отображаемым пользователю. В нем нет ничего особенного, кроме функции returnValue
и вызовов $close
, когда пользователь нажимает кнопки. Сейчас я объясню обе эти вещи.
Когда вы вызываете $close(this)
внутри диалогового окна, диалоговое окно закрывается, и его промис разрешается в результат функции returnValue()
. Обычно диалоговое окно будет содержать форму, и функция returnValue()
будет возвращать любые данные, введенные пользователем в этой форме. В нашем случае формы нет, поэтому функция просто возвращает true. Поскольку мы хотим, чтобы обещание разрешалось в это значение, когда пользователь нажимает ДА, мы вызываем $close(this)
при нажатии на кнопку ДА.
Когда вы вызываете $close(this, ...)
со вторым параметром, например, null или false, промис разрешается с этим значением. В нашем случае мы хотим, чтобы обещание становилось ложным, когда пользователь нажимает НЕТ, поэтому мы вызываем $close(this, false)
при нажатии на кнопку НЕТ. Если бы в диалоговом окне была форма и кнопка ОТМЕНА, вы, вероятно, захотели бы вызвать $close(this, null)
, когда пользователь нажимает кнопку ОТМЕНА.
Вот как вы закрываете диалог и возвращаете значение. Теперь, как вы откроете его и получите обещание? Вы используете функцию openDialog
.
В нашем случае мы стремимся к тому, чтобы этот API открывал диалоговое окно подтверждения:
let yes = await confirm("Are you sure ?");
Давайте посмотрим, как реализована эта асинхронная функция confirm
:
import { openDialog } from 'vue3-promise-dialog'; import ConfirmDialog from '../components/ConfirmDialog.vue'; export async function confirm(text: string) { return await openDialog(ConfirmDialog, {text}); }
Таким образом, чтобы открыть диалог и получить обещание, вы вызываете openDialog
и передаете ему в качестве первого аргумента диалог, который хотите открыть, а в качестве второго аргумента — реквизиты. Функция умна в отношении типов: она выводит тип реквизита из определения компонента и тип возвращаемого значения из функции returnValue()
. Поэтому, если вы передадите неправильный реквизит, ваша IDE будет жаловаться. Если вы ждете обещания и присваиваете значение переменной, эта переменная будет иметь правильный тип, поскольку TypeScript выведет его из назначения.
И вот оно. Надеюсь, вам понравилась статья! Спасибо за чтение.