들어가며
이 글에서는 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-dialog
value 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
작동 원리를 깊이있게 학습할 수 있었습니다.
처음 써보는 글이라 어색한 부분이 많지만 비슷한 문제를 겪고있는 사람들에게 조금이라도 도움이 되었으면 합니다.
잘못된 내용이 있으면 댓글 달아주시면 감사하겠습니다 😊