2 minute read

Vue 컴포넌트 - Slot, Provide/Inject

Vue 컴포넌트 재사용 - Slot

Slot은 컴포넌트 재사용 측면에서 사용되는 것으로, 대부분의 화면 구성은 유사하나 아주 일부분만 바꿔서 여러 군데에서 사용하고 싶을 때 유용합니다.
slot을 통해 하위 컴포넌트의 마크업을 재정의하거나 확장할 수 있습니다.

주로 기본 틀에 해당하는 컴포넌트를 slot을 통해 만들고, 추후 해당 slot을 포함하여 실질적인 컨텐츠에 해당하는 부분만을 작업하는 식으로 작업하게 됩니다.

예시 코드를 통해 slot의 사용 방법에 대해 더 자세히 알아보도록 하겠습니다.
아래는 slot으로 사용할 SlotLayout.vue 코드입니다.

<template>
    <div class="slot-container">
        <header>
            <slot name="header"></slot>
        </header>
        <main>
            <slot></slot>
        </main>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>
</template>

Slot에 name을 지정해서 사용하도록 하였습니다.
이 slot을 활용하여 작업을 할 때는 각 slot에 해당하는 부분만 작업을 하면 되므로, slot에 들어가는 부분을 제외하면 전반적으로 비슷한 디자인을 가지게 됩니다.

slot을 사용하는 컴포넌트에서는 삽입한 컴포넌트 안에서 다음과 같이 template 태그를 사용해 코드를 작성할 수 있습니다.
이 때 코드가 들어갈 slot의 위치를 명시하기 위해 v-slot 디렉티브를 사용합니다.
만약 slot에 name이 없다면, v-slot:default로 지정해주면 됩니다.

이제 위의 slot을 이용하는 컴포넌트 코드를 작성해보도록 하겠습니다.

<template>
    <slot-layout>
        <template v-slot:header>
            <h1>Title in Header</h1>
        </template>
        <template v-slot:default>
            <p> Content </p>
        </template>
        <template v-slot:footer>
            <p> footer </p>
        </template>
    </slot-layout>
</template>
<script>
    import SlotLayout from '../components/SlotLayout.vue';
    export default {
        components: {SlotLayout}
    }
</script>

컴포넌트 안에서 slot 내부에 들어갈 코드를 작성하고 싶으면

위 코드의 결과는 아래 화면처럼 나타납니다.

slot 코드를 아래와 같이 바꿔 부모 컴포넌트에서 작업된 코드와 slot을 통해 따로 작업하지 않고 재사용한 부분을 더 명확히 확인해보도록 하겠습니다.

<template>
    <div class="slot-container">
        <header style="background: yellow">
            <p>header start</p>
            <slot name="header"></slot>
            <p>header end</p>
        </header>
        <main style="background: skyblue">
            <p>main start</p>
            <slot></slot>
            <p>main end</p>
        </main>
        <footer style="background: pink">
            <p>footer start</p>
            <slot name="footer"></slot>
            <p>footer end</p>
        </footer>
    </div>
</template>

slot 내의 header, main, footer 태그에 각각 background color를 주었으며, 태그의 시작과 끝을 나타내는 문구를 추가하였습니다. 그 결과 아래와 같은 화면이 보입니다.

이처럼 특정 디자인을 여러 군데에서 적용하되, 내부에 들어가는 내용을 바꾸고 싶은 경우, slot을 유용하게 사용할 수 있습니다.

Provide / Inject

부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 경우 props를 사용할 수 있습니다. (props 포스팅) 그런데 만약 컴포넌트의 계층 구조가 복잡해지면 어떨까요?
props만으로 데이터를 전달하기가 어려워집니다.

이런 경우 Provide/Inject를 사용해 데이터 전달을 더 쉽게 할 수 있습니다.
데이터를 전달할 부모 컴포넌트에서는 provide를, 자식 컴포넌트에서는 inject를 이용해 데이터를 주고 받는 것입니다.

ParentComponent - ChildComponent - GrandChildComponent가 있다고 해봅시다.
PrarentComponent에서 GrandChildComponent로 props를 이용해 데이터를 전달하려면 중간에 ChildComponent에도 데이터가 전달되어야합니다.
그러나 provide/inject를 사용하면 한 번에 바로 데이터를 전달할 수 있습니다.

최상단의 컴포넌트로 ProvideInjectParent, 이 컴포넌트에서 사용하는 ProvideInjectChild, ProvideInjectChild컴포넌트에서 사용하는 ProvideInjectGrandchild를 생성해보도록 하겠습니다.
이 때 최상단 컴포넌트의 데이터는 중간 컴포넌트에서는 사용하지 않고, provide, inject를 이용해 ProvideInjectGrandchild에서 사용해보도록 하겠습니다.

ProvideInjectParent의 내용은 아래와 같습니다.

<template>
    <ProvideInjectChild />
</template>
<script>
    import ProvideInjectChild from '../components/ProvideInjectChild.vue';
    export default {
        components: {ProvideInjectChild},
        data() {
            return {
                items: ['parent_a', 'parent_b']
            };
        },
        provide() {
            return {
                itemLength: this.items.length,
                items: this.items
            };
        }

    }
</script>

provide() 내에서 제공할 데이터의 이름과 값을 정의하고 있습니다.

중간 컴포넌트인 ProvideInjectChild코드는 아래와 같습니다.

<template>
    <div style="border: 1px solid black">
        <p>In Child</p>
        <ProvideInjectGrandchild/>
    </div>
</template>
<script>
    import ProvideInjectGrandchild from './ProvideInjectGrandchild.vue';
    export default {
        components: {ProvideInjectGrandchild},
    }
</script>

여기에서는 ProvideInjectGrandchild 컴포넌트를 호출할 뿐 따로 데이터를 넘긴다거나 하고있지 않습니다.

마지막으로 ProvideInjectGrandchild 컴포넌트 코드입니다.

<template>
    <div style="border: 1px solid black">
        <p>In Grandchild</p>
        <p>injected data length: {{ this.itemLength }}</p>
        <p>injected data: {{ this.items }}</p>
    </div>
</template>
<script>
    export default {
        inject: ['itemLength', 'items']
    }
</script>

inject: ['itemLength', 'items'] 부분을 통해 사용할 데이터를 받아오고 있습니다.

위에서 작성한 코드를 통해 아래 화면과 같은 결과를 볼 수 있습니다.
부모 컴포넌트에서 정의한 데이터가 잘 전달되었음을 확인할 수 있습니다.