Child Component 에서 v-dialog 사용하기

2wndrhs·2022년 1월 11일
2

Vuetify

목록 보기
1/1
post-thumbnail

Vuetify의 v-dialog 컴포넌트 사용 중 마주친 문제

이 글에서는 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 Component의 Open Dialog 버튼을 눌러도 v-dialog가 열리지 않는다. vue js devtools 를 이용해 좀 더 자세히 살펴보자.

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

value값은 Parent Component에서 v-model을 통해 바인딩 된 dialog 변수의 값과 같다.

따라서 Parnent Component에 선언된 dialog 변수의 값이 이미 true 이기 때문에 Open dialog 버튼을 클릭해도 v-dlalog가 열리지 않는다는 것을 알 수 있다.

여기서 한 가지 의문이 발생한다. v-dialog props문서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 작동 원리를 알게 된 좋은 계기가 된 것 같다.

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

잘못된 내용이나 수정해야될 내용이 있으면 댓글 달아주세요!

1개의 댓글

comment-user-thumbnail
2022년 5월 7일

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

답글 달기