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,
})
}
}
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, '잠시 후 다시 시도해주세요')
])
})
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
}
})
}
})