Самый простой способ работы с модальными диалогами в Vue 3

Модальные диалоги — не такая уж сложная задача для разработки. Они часто используются для подтверждения действий пользователя. Например, удалить какие-либо данные или выполнить авторизацию. Это очень монотонная и неинтересная работа с повторяющейся логикой, которая иногда копируется из компонента в компонент с незначительными изменениями.

Но что если у вас десятки или даже сотни подобных диалогов на одной странице или даже во всем проекте? Или если вам нужно вызывать диалоги по цепочке в зависимости от выбора пользователя? Как сделать многократно используемую функцию, которая также имеет читаемый и сопровождаемый код?

Было бы полезно создать функцию, которая принимала бы компонент диалога и управляла его отрисовкой в шаблоне. Функция возвращала бы объект, содержащий состояние диалога и методы, чтобы с ним можно было работать, как с обещаниями. Например, этот плагин vue-modal-dialogs реализует такую функциональность. К сожалению, он давно не обновлялся и не поддерживает Vue 3.

В этом руководстве я представлю плагин vuejs-confirm-dialog и покажу, как его использовать. Я начну с простых примеров и закончу функцией, которая полностью абстрагирует всплывающий диалог для подтверждения действия и может быть использована в любом компоненте вашего проекта. Примеры написаны на JavaScript для удобства чтения, но сам плагин написан на TypeScript. Плагин полностью типизирован и документирован, что значительно облегчает работу с ним. В отличие от vue-modal-dialogs, этот плагин имеет дополнительную функциональность — специальные хуки: onConfirm и onCancel. Они принимают обратный вызов и вызываются в зависимости от решения пользователя: onConfirm, если пользователь соглашается, и onCancel, если он отказывается.

Конечный результат можно увидеть в песочнице. Код немного отличается от приведенного в посте.

Установка

Начнем с создания нового проекта Vue 3. В консоли:

vue create dialogs-guide

// Pick a second option

? Please pick a preset:
  Default ([Vue 2] babel, eslint)
> Default (Vue 3) ([Vue 3] babel, eslint)
  Manually select features
Войдите в полноэкранный режим Выйти из полноэкранного режима

Мы получили стандартный шаблон для нового проекта. Далее перейдите в папку проекта и установите библиотеку в соответствии с документацией в README.md.

npm i vuejs-confirm-dialog
Вход в полноэкранный режим Выйти из полноэкранного режима

Замените код в main.js на следующий:

import { createApp } from 'vue'
import App from './App.vue'
import * as ConfirmDialog from 'vuejs-confirm-dialog'

createApp(App).use(ConfirmDialog).mount('#app')
Войти в полноэкранный режим Выйти из полноэкранного режима

Использование

Теперь перейдем к файлу App.vue. Сначала исправим код шаблона. Нам нужно добавить в шаблон компонент <DialogsWrapper/>, чтобы плагин работал, и удалить HelloWord:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <DialogsWrapper />
</template>
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте научимся использовать функцию createConfirmDialog. Используйте новый синтаксис setup для раздела script. createConfirmDialog принимает в качестве первого аргумента компонент, который будет модальным диалогом, а в качестве второго — объект, содержащий значения реквизитов компонента. Функция возвращает объект с методами для работы с модальным окном, так метод reveal выводит диалоговое окно, а onConfirm принимает обратный вызов, который будет вызван, если пользователь нажмет кнопку «согласен». Вы можете создать компонент HelloWord, используя логотип, и передать значение свойства msg:

// App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" @click="reveal">
  <DialogsWrapper />
</template>

<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { createConfirmDialog } from 'vuejs-confirm-dialog'

const { reveal } = createConfirmDialog(HelloWorld, { msg: 'Hi!'})
</script>
Войти в полноэкранный режим Выход из полноэкранного режима

Никакой дополнительной логики не требуется. Компонент отображается после вызова функции reveal и исчезает после ответа пользователя на диалог.

Пример из реальной жизни

Теперь давайте напишем что-то более близкое к реальному использованию.

Создадим новый компонент SimpleDialog.vue в папке components:

<template>
  <div class="modal-container">
    <div class="modal-body">
      <span class="modal-close" @click="emit('cancel')">🗙</span>
      <h2>{{ question }}</h2>
      <div class="modal-action">
        <button class="modal-button" @click="emit('confirm')">Confirm</button>
        <button class="modal-button" @click="emit('cancel')">Cancel</button>
      </div>
    </div>
  </div>
</template>

<script setup>
  import { defineProps, defineEmits } from 'vue'

  const props = defineProps(['question'])
  const emit = defineEmits(['confirm', 'cancel'])
</script>

<style>
  .modal-container {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: 100%;
    background-color: #cececeb5;
  }
  .modal-body {
    background-color: #fff;
    border: 2px solid #74a2cf;
    border-radius: 10px;
    text-align: center;
    padding: 20px 40px;
    min-width: 250px;
    display: flex;
    flex-direction: column;
  }
  .modal-action {
    display: flex;
    flex-direction: row;
    gap: 40px;
    justify-content: center;
  }
  .modal-button {
    cursor: pointer;
    height: 30px;
    padding: 0 25px;
    border: 2px solid #74a2cf;
    border-radius: 5px;
    background-color: #80b2e4;
    color: #fff;
  }
  .modal-close {
    cursor: pointer;
    position: relative;
    align-self: end;
    right: -33px;
    top: -17px;
  }
</style>

Вход в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что для правильной работы модального диалога необходимо добавить два входящих события: ['confirm', 'cancel'].

А теперь мы используем их для подтверждения действия, например, для скрытия логотипа. Логика кода, который будет выполнен после согласия пользователя, будет помещена в обратный вызов хука onConfirm.

<template>
  <img v-show="showLogo" alt="Vue logo" src="./assets/logo.png">
  <button @click="reveal">Hide Logo</button>

  <DialogsWrapper />
</template>

<script setup>
import SimpleDialog from './components/SimpleDialog.vue'
import { createConfirmDialog } from 'vuejs-confirm-dialog'
import { ref } from 'vue'

const showLogo = ref(true)

const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, { question: 'Are you sure you want to hide the logo?'})

onConfirm(() => {
  showLogo.value = false
})
</script>
Вход в полноэкранный режим Выход из полноэкранного режима

Повторное использование

Что если у нас много случаев, когда требуется подтверждение какого-либо действия? Нужно ли каждый раз вызывать createConfirmDialog заново?

Нет. Можно написать функцию, которая автоматизирует этот процесс за нас.

// src/composables/useConfirmBeforeAction.js
import SimpleDialog from './../components/SimpleDialog'
import { createConfirmDialog } from 'vuejs-confirm-dialog'

const useConfirmBeforeAction = (action, props) => {
  const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, props)

  onConfirm(action)

  reveal()
}

export default useConfirmBeforeAction
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы используем его для подтверждения следующих внешних ссылок:

// App.vue

<template>
  <ul>
    <li v-for="(link, i) in LINKS" @click="goToLink(link)" :key="i">
      {{ link }}
    </li>
  </ul>
  <DialogsWrapper />
</template>

<script setup>
  import useConfirmBeforeAction from './composables/useConfirmBeforeAction'

  const LINKS = [
    'https://vuejs.org/',
    'https://github.com/',
    'https://vueuse.org/',
  ]

  const goToLink = (link) => {
    useConfirmBeforeAction(
      () => {
        window.location = link
      },
      { question: `Do you want to go to ${link}?` }
    )
  }

</script>
Вход в полноэкранный режим Выход из полноэкранного режима

Заключение

Функция createConfirmDialog облегчает работу с модальными окнами, повторное использование логики и создание цепочек последовательных диалогов. Она заботится о рендеринге модального окна, передаче входящих параметров компоненту и получении данных от него. Он очень гибкий и легко настраивается в соответствии с вашими потребностями.

Это далеко не все его возможности. Например, если концепция хуков вам не близка, вы можете заменить их обещанием, которое возвращает reveal.

И, конечно, для лучшего опыта разработчиков используйте его с TypeScript!

Оцените статью
Procodings.ru
Добавить комментарий