Nuxt에서 유지보수 쉬운 동적 팝업 구현하기

SungMoon, Yoon·2020년 10월 18일
15

Vue.js

목록 보기
3/3
post-thumbnail

Nuxt.js 2.8을 기준으로 작성하였습니다.

완성되지 않은 글입니다. 예고없이 수정될 수 있습니다.

다이얼러그는 GUI에서 유저에게 정보를 보여 주거나 응답을 받는 사용자 인터페이스에서 사용되는 특별한 창이다. 다이얼러그(우리말: 대화상자)라고 부르는 이유는 서비스(컴퓨터)와 유저가 대화하기 위한 인터페이스이기 때문이다.

웹 브라우저는 alert, prompt, confirm등의 고전적인 다이얼러그 API를 window객체에 담아 다이얼러그를 제공한다. 그러나, 고전적인 다이얼러그 타입으로는 힙한 커뮤니케이션 기획과 복잡한 서비스 로직을 담기에 한계가 있다.

위의 두 다이얼러그 디자인의 뼈대는 같지만, 버튼 타입, 버튼 텍스트, 콘텐츠가 모두 다르다. 상황에 따라, Carousel이 올 수도 있다. 겉모습만 다른것은 아니다. 다이얼러그 창이 호출되는 시점도 모두 다를 것이고 각 버튼을 눌렀을 때 불리는 함수도 다를 것이다.

이처럼 여러 case를 대응할 수 있는 다이얼러그를 구현하려면, View 전역에서 활용되기에 다이얼러그를 호출하는 로직은 전역적으로 관리하는것이 좋다. Vue.js는 전역상태관리툴인 Vuex와, Plugin을 조합하여 구현했다.

최상위 컴포넌트에 렌더링 조건 작성하기

<template>
  <div>
    <Dialog v-if="showDialog" />
    <nuxt />
  </div>
</template>

import Dialog from '@/components/Dialog/index.vue'
import { mapState } from 'vuex'

export default {
components: { Dialog },
  computed: {
    ...mapState({
      showDialog: state => state.dialog.visible,
    })
  }
}

Store에 다이얼러그 상태 정의하기

Dialog의 상태값을 저장하는 Store는 visible, template, close, confirm이 있다. 기본값을 미리 정의해두면 다이얼러그 호출시 반복되는 상태값을 옵션으로 넘겨주지 않아도 된다.

// 다이얼러그 렌더링 조건
visible: boolean,
// 다이얼러그 body 렌더링 될 컨텐츠
template: VNode,
close: {
  // 버튼 텍스트
  text: '확인',
  // 디자인시스템에 정의된 버튼 타입
  buttonType: 'secondary',
  // loading state
  loading: false,
  // 클릭 이벤트 핸들러
  onClick: null | function
},
confirm: // close와 동일한 구조

다이얼러그 템플릿 정의하기

Vue.js 는 템플릿을 통해 HTML마크업으로 컴포넌트를 빌드하는것을 추천한다. 그러나, 특수한 상황에 $createElement API를 이용하여 JavaScript로 컴포넌트를 빌드할 수 있다. 다이얼러그 템플릿의 컨텐츠(예를 들어, "정말 로그아웃 하실건가요?")를 렌더링하는 함수를 반환하는 함수를 미리 작성하여 모아두면 유지보수가 수월해진다.

/* dialogTemplate/index.js */
import Vue from 'vue'
// 미리 정의된 디자인 시스템의 Paragraph Component
import p from '@/components/Typography/ParagraphDialog.vue'

const vue = new Vue()
const h = vue.$createElement

import _userLogin from './lib/user.login.js'
export const USER_LOGIN = () => _userLogin(h, p)

/* dialogTemplate/user.logout.js */
export default (h, p) => ({
  confirm: h('div', [
    h(p, [h('strong', `정말 로그아웃 하실 건가요? 😔`)]),
    h(p, '실수일 수도 있으니까요')
  ]),
  success: h('div', [
    h(p, [h('strong', `OK, 로그아웃 완료 😊`)]),
    h(p, '보고싶으니까 다시 만나요!')
  ]),
  fail:h('div', [
    h(p, [h('strong', `로그아웃 실패 💦`)]),
    h(p, '잠시 후 다시 시도해주세요')
  ])
})

Virtual DOM를 렌더링 하는 다이얼러그 Vue컴포넌트 작성하기

Created Hook에서 템플릿 렌더 함수가 반환한 Virtual DOM을 $slot에서 렌더링 할 수 있다.

/* @/components/Dialog.vue */
<template>
  ...
  <slot v-if="$slots.template" name="template"></slot>  
  ...
</template>

import { mapState } from 'vuex'

export default Vue.extend({
  computed: {
    ...mapState({
      dialog: state => state.dialog
    })
  },
  created() {
    if (typeof this.dialog.template === 'object') {
      this.$slots.template = this.dialog.template
    }
  }
})

다이얼러그 호출함수 정의하기

/* [nuxt.js project directory]/plugins/dialog.js */
import Vue from 'vue'

export default (context, inject) => {
  inject('dialog', ({ template, close, confirm }) => {
    if (context.store.state.dialog.visible) {
      context.store.commit('dialog/off')
    }
    Vue.nextTick(() => {
      context.store.commit('dialog/on', {
        template,
        close,
        confirm,
      })
    })
  })
  
  inject('dialogOff', () => {
    context.store.commit('dialog/off')
  })
}

다이얼러그 호출하기

import {
  USER_LOGOUT as USER_LOGOUT_DIALOG_TEMPLATE,
} from '@/components/Dialog/Templates'

export default Vue.extend({
  logout() {
    this.$dialog({
      template: USER_LOGOUT_DIALOG_TEMPLATE().confirm,
      close: {
        text: '닫기',
        onClick: this.$dialogOff
      },
      confirm: {
        text: '로그아웃',
        onClick: this.onClickLogout
      }
    })
  }
})

적용 결과

0개의 댓글