Vuetify v-dialog 제대로 사용하기

이중곤·2022년 1월 11일
6

Vuetify

목록 보기
1/1
post-thumbnail

들어가며


이 글에서는 Vuetifyv-dialog 컴포넌트를 사용하면서 발생한 문제를 다루려고 합니다.

v-dialog 컴포넌트와 v-dialog의 트리거를 한 컴포넌트 안에서만 사용했을 때는 문제가 없었지만 Parent Componentv-dialog의 트리거를 작성하고 Child Componentv-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 Componentv-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를 닫으면 정상적으로 동작하지만 vueitfyv-dialogv-dialog 영역 바깥 부분을 클릭해도 v-dialog가 닫히게 동작합니다.

이러한 방식으로 v-dialog를 닫게 되면 문제가 발생하는데 다시 v-dialog를 열기 위해 Parent ComponentOpen Dialog 버튼을 눌러도 v-dialog가 열리지 않습니다. vue js devtools 를 이용해 좀 더 자세히 살펴보겠습니다.

v-dialog 영역의 바깥 부분을 클릭하여 v-dialog를 닫았지만 v-dialogprops로 내려준 value의 값이 true인 것을 확인할 수 있습니다.

value값은 Parent Component에서 v-model을 통해 바인딩 된 dialog 변수의 값과 같습니다. 즉, Parent Component에 선언된 dialog 변수의 값이 이미 true 이기 때문에 Open dialog 버튼을 클릭해도 v-dlalog가 열리지 않는다는 것을 알 수 있습니다.

여기서 한 가지 의문이 생깁니다. v-dialog 문서value prop를 보면 value propcomponentvisibility를 결정한다고 나와있지만 v-dialogvalue prop의 값이 true임에도 v-dialog가 보이지 않습니다 🤔

그 이유는 v-dialog의 내부 동작 원리에서 찾을 수 있었습니다. 마찬가지로 vue js devtools 를 이용해 좀 더 자세히 살펴보겠습니다.

v-dialog 영역의 바깥 부분을 클릭하면 v-dlalog 내부의 데이터 값인 isActive 값이 false로 바뀌고 v-dialog 는 닫힙니다.

즉, v-dialogvisibility 를 결정하는 것은 isActive 임을 알 수 있습니다.

결론적으로 이 문제는 v-dialogisActive 값이 Parent Component dialog 변수와 독립적이기 때문에 발생한다는 것을 추론할 수 있습니다.

해결방법


v-dialog 영역의 바깥 부분을 클릭하면 v-dialog 로 부터 isActive 값이 payloadinput 이벤트가 발행됩니다.

따라서 이 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>
  • ChildComponentdialog 변수의 값을 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에 선언한 dialoggetter를 이용해 받아줍니다.

  • dialogsetterdialog의 값이 업데이트 될 때마다 Parent Component에게 input 이벤트와 함께 payload로 업데이트 된 dialog 값을 전달합니다.

  • v-dialog 컴포넌트는 dialog의 값을 value prop으로 받고 내부 데이터 값인 isActive를 업데이트 합니다.

  • 이 때 isActive 값이 바뀔 경우 내부적으로 input 이벤트를 발행하여 ChildComponentdialog의 값을 isActive 값과 같게 만들어줍니다.

요약


Parent Component의 Open Dialog 버튼을 누를 경우

  1. Parent Componentdialog 값이 true로 바뀜
  2. Child Componentdialog 값을 value prop으로 내려줌
  3. Child Componentcomputed에 선언한 getter로 인해 dialog 값이 true로 바뀜
  4. v-dialogvalue prop으로 dialog 값을 내려줌
  5. v-dialog value prop과 바인딩 된 내부 데이터 값 isActive 값이 true로 바뀜
  6. Dialog 가 열림

v-dialog 영역 바깥 부분을 클릭할 경우

  1. v-dialog 내부 데이터 값인 isActive값이 false로 바뀜
  2. v-dialog 컴포넌트에서 내부적으로 input 이벤트와 함께 payloadisActive 값을 전달
  3. Child Component 에서 input 이벤트를 받아 dialog 값을 false로 바꿈
  4. Child Componentcomputed에 선언한 setter로 인해 input 이벤트와 함께 payloaddialog의 값을 전달
  5. Parent Component에서 input 이벤트를 받아 dialog 값을 false로 바꿈
  6. Dialog 가 닫힘

이제 v-dialog가 원하는 대로 작동함을 알 수 있습니다.

하지만 어디서 많이 본 듯한 구조인 value propinput 이벤트를 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-modelvuetifyv-dialog 작동 원리를 깊이있게 학습할 수 있었습니다.

처음 써보는 글이라 어색한 부분이 많지만 비슷한 문제를 겪고있는 사람들에게 조금이라도 도움이 되었으면 합니다.

잘못된 내용이 있으면 댓글 달아주시면 감사하겠습니다 😊

4개의 댓글

comment-user-thumbnail
2022년 5월 7일

좋은 글 감사합니다. 최고^^

답글 달기
comment-user-thumbnail
2022년 7월 29일

기가막히네요..

답글 달기
comment-user-thumbnail
2023년 8월 3일

탬플릿의 한계랄까요.. 모달하나여는데 이렇게까지 풀어써야한다니

답글 달기
comment-user-thumbnail
2024년 1월 19일

정말 감사합니다 선생님 근데 vue3기준으로 하니까 부모 컴포넌트에서 v-model을 쓰면 동작하지 않고
:value , @input 을 쓰면 잘 되네요 제가 이해가 부족해서 그런거 같기도 한데 아무튼 감사합니다!!!

답글 달기