2025. 3. 22. 22:33ㆍFrontend/Vue.js
Vue를 쓰면서 함께 Vuetify도 쓰고 있는데 쓰다보니 슬롯을 활용하는 VMenu 컴포넌트를 쓰게 됐는데 동작이 어떻게 되는지 헷갈려서 정리하는 김에 글을 쓴다. 공식 문서로 봤을 때는 React의 children 이랑 비슷한 느낌으로 알았는데 제대로 쓰려고 하니 오래돼서 그런지 헷갈린다.
슬롯이란?
슬롯(slot)은 Vue에서 제공하는 기능으로 컴포넌트의 재활용성과 유연성을 제공한다. 형태는 정해져 있고 내부 컨텐츠가 달라지는 경우에 유용하게 쓸 수 있다. 공식 문서의 예를 빌리면 다음과 같다.
<!-- BaseLayout.vue -->
<div class="container">
<header>
<!-- 우리는 여기에 헤더 컨텐츠를 원합니다. -->
</header>
<main>
<!-- 우리는 여기에 메인 컨텐츠를 원합니다. -->
</main>
<footer>
<!-- 우리는 여기에 푸터 컨텐츠를 원합니다. -->
</footer>
</div>
위처럼 레이아웃이 정해져 있고 내부 컨텐츠가 동적으로 변해야 하는 상황에 슬롯을 사용하기 적합하다. 이런 경우에 슬롯을 적용하면 다음과 같이 사용할 수 있다.
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- default -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
이렇게 만들어 놓은 BaseLayout을 사용할 때는 다음과 같이 사용할 수 있다.
<!-- 부모 컴포넌트 -->
<BaseLayout>
<template v-slot:header>
<span>헤더입니다</span>
</template
<p>메인입니다</p> <!-- 암시적으로 default에 바인딩됨 -->
<template v-slot:footer>
<span>푸터입니다</span>
</template
</BaseLayout>
명시적으로 default를 지정할 수도 있다.
<!-- 부모 컴포넌트 -->
<BaseLayout>
<template #header> <!-- #은 v-slot:의 단축 문법 -->
<span>헤더입니다</span>
</template>
<template v-slot:default>
<p>메인입니다</p>
</template>
<template v-slot:footer>
<span>푸터입니다</span>
</template
</BaseLayout>
슬롯의 전체적인 구조를 그림으로 보면 다음과 같다.
슬롯을 활용할 때 자식에게 props로 값을 넘겨줄 수도 있다.
<!-- MyComponent.vue -->
<script setup>
const greetingMessage = '안녕'
</script>
<template>
<div>
<slot :text="greetingMessage" :count="1" normal="normal" />
</div>
</template>
slot의 v-bind:text 로 값을 greetingMessage 로 설정, v-bind:count 로 값을 1 로, 단순 값으로 normal 속성에 "normal" 을 설정해놓았다. MyComponent를 실제로 사용할 때 다음과 같이 사용할 수 있다.
<template>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }} {{ slotProps.normal }}
</MyComponent>
</template>
slotProps에는 text와 count의 값이 있고 이를 활용해서 위와 같이 사용한 것이다.
이제 내가 헷갈렸던 코드를 살펴보자.
<v-menu>
<template #activator="{ props }">
<v-btn
color="primary"
v-bind="props"
>
Activator slot
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
:value="index"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
이 코드에서는 v-menu 가 슬롯을 사용하는 컴포넌트이고 내부에서 slotProps를 #activator에서 사용할 수 있게 전달해준다. {prop}로 작성한 구문은 구조분해할당 문법으로 slotProps로 전달받은 값들 중 props만 빼낸 것이다. 실제 slotProps를 보면 다음과 같다.
이제 props를 Menu를 활성화시킬 요소(activator)에 v-bind로 바인딩해서 VMenu를 사용하도록 공식 문서에서 설명하고 있다. 실제로 props만 버튼에 바인딩하면 버튼을 클릭했을 때 메뉴가 열리는데, 이는 props에 onClick과 같은 이벤트 처리 함수가 담겨있기 때문이다.
아마 VMenu의 내부 코드를 예상해보면 다음과 같을 것이다.
<!-- VMenu.vue -->
<script setup>
// ...
</script>
<template>
// ...
<slot name="activator" isActive="false" props="{ ... }" targetRef="{ ... }" />
// ...
</template>
자세한 사항은 공식 문서를 참고하기 바란다.
https://ko.vuejs.org/guide/components/slots.html#slots
Vue.js
Vue.js - 프로그래시브 자바스트립트 프레임워크
ko.vuejs.org
마치며
공식 문서로 볼 때 헷갈렸던 게 부모 컴포넌트랑 슬롯 컴포넌트를 계속 번갈아가면서 보니 데이터가 어디로 흘러가는지 헷갈렸다. 분명 탑다운 방식으로 데이터가 흘러간다고 봤었는데 emit 같이 부모에 이벤트같이 전달할 방법이 있다보니 더 헷갈렸던 것 같다.
뷰를 써본 지 5일 정도 된 거 같은데 기본 문법 정도는 좀 익혔지만 제대로 활용하려고 하니 계속 막히게 된다. 막힐 때마다 이렇게 딥다이브해서 공부하니 집중력도 올라가고 공부하는 재미가 있다.
'Frontend > Vue.js' 카테고리의 다른 글
[Vue.js] unplugin-vue-components (0) | 2025.03.19 |
---|---|
[Vue.js] unplugin-auto-import (0) | 2025.03.19 |
[Vue.js] Unplugin-Vue-Router (0) | 2025.03.19 |