Slot 과 v-slot 기능 포스팅


:raising_hand: Vue 개발 중 공부내용을 기록하는 포스트 입니다.


1. 개요


간단한 TodoListVue로 개발하면서 slot의 기능에 대해서 다시한번 정리해본다.

2. 구성


- todoProject
  ... 생략
  - src
    ... 생략
    - App.js
    - components
      ... 생략
      - TodoInput.vue
      - common
        - Modal.vue
2-1. App.js

우선 구성은 App.jsContainer Component의 역할을 한다.

모든 상태관리는 App.js에서 이뤄지고 components 폴더 내 ToDoList, TodoInput, TodoHeader ... 등등의 Presentational Component에서는 변경 해달라고 emit을 통해서 요청을 하는 형태이다.

2-2. TodoInput.vue

TodoInput.vue 컴포넌트에서 이번에 볼 부분은 slot을 사용한 부분이다.

<!-- Modal.vue  -->
<transition name="modal">
  <div class="modal-mask">
    <div class="modal-wrapper">
      <div class="modal-container">
        <div class="modal-header">
          <slot name="header"> default header </slot>
        </div>

        <div class="modal-body">
          <slot name="body"> default body </slot>
        </div>

        <div class="modal-footer">
          <slot name="footer">
            default footer
            <button class="modal-default-button" @click="$emit('close')">
              OK
            </button>
          </slot>
        </div>
      </div>
    </div>
  </div>
</transition>
<!-- TodoInput.vue  -->
<!-- ... 생략  -->
<Modal v-if="showModal" @close="showModal = false">
  <!--
      you can use custom content here to overwrite
      default content
    -->
  <h3 slot="header">경고!</h3>
</Modal>
<!-- ... 생략  -->

여기서 TodoInput.vueModal.vue의 부모 컴포넌트가 된다.

Modal.vue에서 <slot name="header"> 를 통해 default headerslot을 먼저 정의해 놓았고 우리는 이러한 Modal.vue를 사용하면서 사용자의 재정의가 가능한 것이다.

image

위에 이미지에서 보이듯이 우리가 TodoInput.vue에서 재정의한 header 부분만 다르게 나타난다.

3. slot-scope


slot을 통해서 자식 컴포넌트에서 사용된 데이터 혹은 메서드를 사용할 수도 있다.

아래의 예제를 보자

<!-- 자식 컴포넌트  -->
<!-- ... 생략  -->
<div class="modal-body">
  <slot name="body" :modalData="modalData" :consoleMessage="consoleMessage">
    default body
  </slot>
</div>

<script>
  export default {
    data() {
      return {
        modalData: ["modalData1", "modalData2"],
      };
    },
    methods: {
      consoleMessage() {
        console.log("Console Message!");
      },
    },
  };
</script>
<!-- ... 생략  -->
<!-- 부모 컴포넌트  -->
<!-- ... 생략  -->

<Modal v-if="showModal" @close="showModal = false">
  <!--
      you can use custom content here to overwrite
      default content
    -->
  <h3 slot="body" slot-scope="slotScope">
    
    <button v-on:click="slotScope.consoleMessage">Console Button</button>
  </h3>
</Modal>

<!-- ... 생략  -->

위 소스에서 보면 Child Component에서 body Slot에서 modalDataconsoleMessage 메서드를 정의하고 Parent Component에서 사용되는걸 볼 수 있다.

image

그리고 Console Button을 클릭하면 Child Component에서 정의한 ConsoleMessage 함수가 실행되어 console 메시지가 찍히는걸 볼 수 있다.

image

3-1. v-slot


Vue3 에서는 slot, slot-scope가 공식적으로 삭제 된다고 한다.

같은 기능을 제공하는 Vue 디렉티브 v-slot로 동일한 기능을 구현해보자.

child Component는 동일하고 Parent Component에 변화가 생긴다.

<!-- 부모 컴포넌트  -->
<!-- slot-scope 사용 -->

<Modal v-if="showModal" @close="showModal = false">
  <!--
      you can use custom content here to overwrite
      default content
    -->
  <h3 slot="body" slot-scope="slotScope">
    
    <button v-on:click="slotScope.consoleMessage">Console Button</button>
  </h3>
</Modal>

<!-- v-slot 사용 -->

<Modal v-if="showModal" @close="showModal = false">
  <!--
      you can use custom content here to overwrite
      default content
    -->
  <template v-slot:body="slotProps">
    <h3>
      
      <button v-on:click="slotProps.consoleMessage">Console Button</button>
    </h3>
  </template>
</Modal>

image

v-slot을 사용할 때는 v-slot:[name] 으로 사용하고 template 태그로 감싸고 그 안에서 사용해야 한다는 점이 차이가 있다.

slot의 기능은 실무에서도 자주 쓰이기 때문이 이번 Modal을 진행하면서 다시한번 리마인드 겸 정리하도록 하였다.

최종 적용예


하나의 예를 더 진행한다.

상위 컴포넌트 : UserView, ItemView 하위 컴포넌트 : UserProfile.vue

UserView, ItemView 두개의 컴포넌트에서 하나의 컴포넌트를 재사용 하는 경우이다.

하지만, 각각의 상위 컴포넌트에서 데이터props로 내려주는 방식으로 진행했을 때 각각의 경우마다 분기처리 해줘야되는 번거로움이 생긴다.

이럴경우 slot을 활용해서 상위 컴포넌트에서 하위 컴포넌트에 들어갈 내용을 정의하면 된다.

그렇게되면 상위 컴포넌트에서 가지고 있는 데이터로 재사용된 컴포넌트를 채울 수 있기 때문이다 아래 코드를 보자.

<!-- 상위 컴포넌트(UserView) -->

<template>
  <div>
    <user-profile :itemInfo="itemInfo">
      <div slot="username"></div>
      <template slot="time"></template>
    </user-profile>
  </div>
</template>

<!-- 상위 컴포넌트(ItemView) -->
<div>
  <section>
    <!-- 질문 상세 정보 -->
    <user-profile :itemInfo="itemInfo">
      <div slot="username"></div>
      <template slot="time"></template>
    </user-profile>
    <section>
      <h2></h2>
    </section>
  </section>

  <!-- 하위 컴포넌트(UserProfile) -->
  <template>
    <div>
      <user-profile :itemInfo="itemInfo">
        <div slot="username"></div>
        <template slot="time"></template>
      </user-profile>
    </div>
  </template>
</div>

위와 같이 하위 컴포넌트에서 실제로 필요한 위치에 각각의 name을 갖는 slot 태그를 만들었다.

이후 상위 컴포넌트의 재사용하는 user-profile 태그에 각각의 용도에 맞춰 name 프로퍼티를 넣고 실제 들어가야할 tag를 정의한다.

작은거라도 하나하나 정리해가다보면 점점 내것이 되는것 같아 기분이 좋다.

참고 사이트