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