Vue - Teleport

김영준·2023년 8월 7일
0

TIL

목록 보기
70/91
post-thumbnail

아래 에제는 modal 창을 구현한 예제이다.

// App.vue

<template>
  <Modal width="300px">
    <template #activator>
      <button>On Modal!</button>
    </template>
    <h3>App.vue</h3>
  </Modal>
  <Hello />
</template>

<script>
import Hello from "~/components/Hello";

export default {
  components: {
    Hello,
  },
  data() {
    return {
      msg: "Hello Vue!",
    };
  },
};
</script>
// Modal.vue

<template>
  <div @click="onModal">
    <slot name="activator"></slot>
  </div>
  <template v-if="isShow">
    <div
      class="modal"
      @click="offModal">
      <div
        :style="{ width: `${parseInt(width, 10)}px`}"
        class="modal__inner"
        @click.stop>
        <slot></slot>
      </div>
    </div>
  </template>
</template>

<script>
export default{
    props:{
        width: {
            type: [String, Number],
            default: 400
        }
    },
    data() {
        return {
            isShow: false
        };
    },
    methods: {
        onModal(){
            this.isShow = true;
        },
        offModal(){
            this.isShow = false;
        }
    }
};
</script>

<style lang="scss" scoped>
.modal {
    background-color: rgba(black, .5);
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 9;
    display: flex;
    justify-content: center;
    align-items: center;
    &__inner{
        background-color: white;
        box-sizing: border-box;
        padding: 20px;
    }
}</style>


하지만 현재 modal을 fixed로 사용했지만 만약 상위 요소에 transform이나 filter 등의 스타일 속성이 있으면 제대로 출력이 되지 않는다.

이럴 때 사용할 수 있는 VueJS의 기능이 Teleport이다.

컴포넌트 내부의 데이터를 온전하게 사용하면서 원하는 위치에서 <teleport>로 감싼 요소를 출력할 수 있다.

to 속성으로 body요소 내부로 순간이동 시켰다.


예제 최종 코드
textarea에 자동으로 focus를 주기위해 modal 온오프 변수를 상위 컴포넌트에서 관리

// App.vue

<template>
  <div style="transform: scale(1);">
    <Modal
      v-model="isShow"
      width="300px">
      <template #activator>
        <button>On Modal!</button>
      </template> 
      <h3>App.vue</h3>
    </Modal>
    <Hello />
  </div>
</template>

<script>
import Hello from "~/components/Hello";

export default {
  components: {
    Hello,
  },
  data() {
    return {
      isShow: false,
      msg: "Hello Vue!",
    };
  },
};
</script>
// Hello.vue
<template>
  <Modal
    v-model="isShow"
    closeable>
    <template #activator>
      <h1>Hello</h1>
    </template>
    <h3>Hello.vue</h3>
    <textarea
      ref="editor"
      v-model="msg"></textarea>
    <button @click="submit">
      Submit!
    </button>
  </Modal>
</template>

<script>
export default {
  data(){
    return {
      msg: "Pleas enter the text.",
      isShow: false
    };
  },
  watch: {
    isShow(newValue) {
      if(newValue) {
        this.$nextTick(() => {
          this.$refs.editor.focus();  
        });
        
      }
    }
  },
  methods: {
    submit(){
      console.log(this.msg);
    }
  }
};
</script>
<style scoped lang="scss">
$color: red;
h1 {
  color: $color;
}
textarea{
  width:100%;
  height:100px;
  box-sizing: border-box;
}
</style>
// Modal.vue

<template>
  <div @click="onModal">
    <slot name="activator"></slot>
  </div>
  <teleport to="body">
    <template v-if="modelValue">
      <div
        class="modal"
        @click="offModal">
        <div
          :style="{ width: `${parseInt(width, 10)}px`}"
          class="modal__inner"
          @click.stop>
          <button
            v-if="closeable"
            class="close"
            @click="offModal">
            x
          </button>
          <slot></slot>
        </div>
      </div>
    </template>
  </teleport>
</template>

<script>
export default{
    props:{
        modelValue: {
            type:Boolean,
            default: false
        },
        width: {
            type: [String, Number],
            default: 400
        },
        closeable: {
            type:Boolean,
            default:false
        }
    },
    emits:["update:modelValue"],
    watch: {
        modelValue(newValue){
            if(newValue){
                window.addEventListener("keyup", this.keyupHandler);
            } else{
                window.removeEventListener("keyup", this.keyupHandler);
            }
        }
    },
    methods: {
        keyupHandler(event){
            if(event.key === "Escape"){
                this.offModal();
            }
        },
        onModal(){
            this.$emit("update:modelValue", true);
        },
        offModal(){
            this.$emit("update:modelValue", false);
        }
    }
};
</script>

<style lang="scss" scoped>
.modal {
    background-color: rgba(black, .5);
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 9;
    display: flex;
    justify-content: center;
    align-items: center;
    &__inner{
        background-color: white;
        box-sizing: border-box;
        border-radius: 6px;
        box-shadow: 0 10px 10px rgba(black, .2);
        padding: 20px;
        button.close{
            float: right
        }
    }
}
</style>
profile
꾸준히 성장하는 개발자 블로그

0개의 댓글