들어가며
이 글에서는 Vuetify의 v-dialog 컴포넌트를 사용하면서 발생한 문제를 다루려고 합니다.
v-dialog 컴포넌트와 v-dialog의 트리거를 한 컴포넌트 안에서만 사용했을 때는 문제가 없었지만 Parent Component에 v-dialog의 트리거를 작성하고 Child Component에 v-dialog 컴포넌트를 작성하니 문제가 발생했습니다.
아래의 코드를 보면서 어떤 문제가 발생했는지 살펴보도록 하겠습니다.
예시 코드
ParentComponent:
<template>
<div>
<v-btn color="accent" large @click.stop="dialog = true">Open Dialog</v-btn>
<child-component v-model="dialog" @close="dialog = false"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
dialog: false,
};
},
components: {
ChildComponent,
},
};
</script>Child Component에v-model을 이용해 현재 dialog의 상태를 나타내는dialog변수를 바인딩해 주었습니다.
Child Component:
<template>
<v-dialog v-bind="$attrs" max-width="500px">
<v-card>
<v-card-actions>
<v-btn color="primary" text @click.stop="$emit('close')">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {};
</script>Parent Component에서 내려준dialog의 상태는Child Component에서v-bind="$attrs"를 통해 받아줍니다.
v-dialog 컴포넌트의 v-btn을 클릭하면 close 이벤트가 발행되고 Parent Component에서 이벤트를 감지해 dialog의 상태를 false로 만듭니다.
발생하는 문제
버튼을 클릭하여 v-dialog를 닫으면 정상적으로 동작하지만 vueitfy의 v-dialog는 v-dialog 영역 바깥 부분을 클릭해도 v-dialog가 닫히게 동작합니다.
이러한 방식으로 v-dialog를 닫게 되면 문제가 발생하는데 다시 v-dialog를 열기 위해 Parent Component의 Open Dialog 버튼을 눌러도 v-dialog가 열리지 않습니다. vue js devtools 를 이용해 좀 더 자세히 살펴보겠습니다.

v-dialog 영역의 바깥 부분을 클릭하여 v-dialog를 닫았지만 v-dialog에 props로 내려준 value의 값이 true인 것을 확인할 수 있습니다.
이 value값은 Parent Component에서 v-model을 통해 바인딩 된 dialog 변수의 값과 같습니다. 즉, Parent Component에 선언된 dialog 변수의 값이 이미 true 이기 때문에 Open dialog 버튼을 클릭해도 v-dlalog가 열리지 않는다는 것을 알 수 있습니다.
여기서 한 가지 의문이 생깁니다. v-dialog 문서의 value prop를 보면 value prop이 component의 visibility를 결정한다고 나와있지만 v-dialog의 value prop의 값이 true임에도 v-dialog가 보이지 않습니다 🤔
그 이유는 v-dialog의 내부 동작 원리에서 찾을 수 있었습니다. 마찬가지로 vue js devtools 를 이용해 좀 더 자세히 살펴보겠습니다.

v-dialog 영역의 바깥 부분을 클릭하면 v-dlalog 내부의 데이터 값인 isActive 값이 false로 바뀌고 v-dialog 는 닫힙니다.
즉, v-dialog의 visibility 를 결정하는 것은 isActive 임을 알 수 있습니다.
결론적으로 이 문제는 v-dialog의 isActive 값이 Parent Component 의 dialog 변수와 독립적이기 때문에 발생한다는 것을 추론할 수 있습니다.
해결방법

v-dialog 영역의 바깥 부분을 클릭하면 v-dialog 로 부터 isActive 값이 payload 인 input 이벤트가 발행됩니다.
따라서 이 input 이벤트를 Child Component 에서 받아 다시 Parent Component 로 이벤트를 발행하여 dialog 변수의 값을 업데이트하는 방식으로 문제를 해결할 수 있습니다.
예시 코드
ParentComponent:
<template>
<div>
<v-btn color="accent" large @click.stop="dialog = true">Open Dialog</v-btn>
<child-component :value="dialog" @input="dialog = $event"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
dialog: false,
};
},
components: {
ChildComponent,
},
};
</script>ChildComponent에dialog변수의 값을value prop으로 내려주고ChildComponent에서 발행된input이벤트를 받아dialog값을payload에 담긴value의 값으로 업데이트 합니다.
ChildComponent:
<template>
<v-dialog :value="dialog" @input="dialog = $event" max-width="500px">
<v-card>
<v-card-actions>
<v-btn color="primary" text @click.stop="dialog = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
required: true,
},
},
computed: {
dialog: {
get() {
return this.value;
},
set(value) {
this.$emit('input', value);
},
},
},
};
</script>-
Parent Component에서 내려준value값을computed에 선언한dialog의getter를 이용해 받아줍니다. -
dialog의setter는dialog의 값이 업데이트 될 때마다Parent Component에게input이벤트와 함께payload로 업데이트 된dialog값을 전달합니다. -
v-dialog컴포넌트는dialog의 값을value prop으로 받고 내부 데이터 값인isActive를 업데이트 합니다. -
이 때
isActive값이 바뀔 경우 내부적으로input이벤트를 발행하여ChildComponent의dialog의 값을isActive값과 같게 만들어줍니다.
요약
Parent Component의 Open Dialog 버튼을 누를 경우
Parent Component의dialog값이true로 바뀜Child Component에dialog값을value prop으로 내려줌Child Component의computed에 선언한getter로 인해dialog값이true로 바뀜v-dialog에value prop으로dialog값을 내려줌v-dialogvalue prop과 바인딩 된 내부 데이터 값isActive값이true로 바뀜- Dialog 가 열림
v-dialog 영역 바깥 부분을 클릭할 경우
v-dialog내부 데이터 값인isActive값이false로 바뀜v-dialog컴포넌트에서 내부적으로input이벤트와 함께payload로isActive값을 전달Child Component에서input이벤트를 받아dialog값을false로 바꿈Child Component의computed에 선언한setter로 인해input이벤트와 함께payload로dialog의 값을 전달Parent Component에서input이벤트를 받아dialog값을false로 바꿈- Dialog 가 닫힘
이제 v-dialog가 원하는 대로 작동함을 알 수 있습니다.
하지만 어디서 많이 본 듯한 구조인 value prop과 input 이벤트를 v-model 을 이용하여 더 간단하게 나타낼 수 있을 것 같습니다.
예시 코드
Parent Component:
<template>
<div>
<v-btn color="accent" large @click.stop="dialog = true">Open Dialog</v-btn>
<child-component v-model="dialog"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
dialog: false,
};
},
components: {
ChildComponent,
},
};
</script>Child Component:
<template>
<v-dialog v-model="dialog" max-width="500px">
<v-card>
<v-card-actions>
<v-btn color="primary" text @click.stop="dialog = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
required: true,
},
},
computed: {
dialog: {
get() {
return this.value;
},
set(value) {
this.$emit('input', value);
},
},
},
};
</script>v-model="dialog"로 수정해도 정상적으로 작동하는 것을 알 수 있습니다.
v-model 의 자세한 동작 원리는 여기를 참고하시면 좋을 것 같습니다.
마치며
이번 문제를 해결하면서 v-model과 vuetify의 v-dialog 작동 원리를 깊이있게 학습할 수 있었습니다.
처음 써보는 글이라 어색한 부분이 많지만 비슷한 문제를 겪고있는 사람들에게 조금이라도 도움이 되었으면 합니다.
잘못된 내용이 있으면 댓글 달아주시면 감사하겠습니다 😊