В этой статье я расскажу вам, какие библиотеки компонентов, такие как PrimeVue и Vuetify, предлагают для работы с диалогами. Тогда я скажу вам, что я думаю, что это лучший способ сделать это. Наконец, я покажу вам, как использовать vue3-promise-dialog.

Диалоги как обычно

Большинство библиотек компонентов предлагают компонент Dialog.

Это пример от PrimeVue:

И еще один от Vuetify:

Как видите, библиотеки компонентов поощряют встраивание кода диалога (шаблона и логики) прямо в родительский компонент, открывающий его. У этого есть несколько минусов:

  • Это делает невозможным создание многоразовых диалогов, поскольку каждый диалог привязан к своему открывающемуся сайту.
  • Логика и шаблон диалога смешаны с логикой и шаблоном его родительского компонента. Это становится еще более беспорядочным, когда родительский компонент должен открывать несколько диалогов.
  • Невозможно открыть диалог из файла 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')

Затем добавьте DialogWrappercomponent в конце корневого компонента вашего приложения:

<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 выведет его из назначения.

И вот оно. Надеюсь, вам понравилась статья! Спасибо за чтение.