Vue3 <slot> element

์›ฐ์น˜์Šคยท2025๋…„ 3์›” 18์ผ

Vue.js

๋ชฉ๋ก ๋ณด๊ธฐ
3/4
post-thumbnail

๐ŸŽฏ <slot> ๊ฐœ๋…


1๏ธโƒฃ ๊ธฐ๋ณธ ๊ฐœ๋…

Vue.js ๊ณต์‹ ์‚ฌ์ดํŠธ slot
โ—ผ๏ธ slot์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ HTML ์ฝ˜ํ…์ธ ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
โ—ผ๏ธ slot์„ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ณ , ์œ ์—ฐํ•œ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.


2๏ธโƒฃ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

โ—ผ๏ธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ <slot>์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ „๋‹ฌํ•œ ๋‚ด์šฉ์„ ์›ํ•˜๋Š” ์œ„์น˜์— ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

์ž์‹ ์ปดํฌ๋„ŒํŠธ (Child.vue)

<template>
  <div class="box">
    <slot></slot>
  </div>
</template>

<style scoped>
.box {
  padding: 10px;
  border: 1px solid black;
}
</style>

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ (Parent.vue)

<template>
  <Child>
    <p>์ด ๋‚ด์šฉ์ด slot์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค!</p>
  </Child>
</template>

<script setup>
import Child from './Child.vue';
</script>

๐Ÿš€ ๊ฒฐ๊ณผ: <slot>์ด ์žˆ๋Š” ๋ถ€๋ถ„์— <p>์ด ๋‚ด์šฉ์ด slot์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค!</p>๊ฐ€ ๋ Œ๋”๋ง๋œ๋‹ค.


3๏ธโƒฃ โญ์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ (Named Slots)

โ—ผ๏ธ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šฌ๋กฏ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, name์„ ์ง€์ •ํ•˜์—ฌ ํŠน์ • ์Šฌ๋กฏ์— ์ฝ˜ํ…์ธ ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ž์‹ ์ปดํฌ๋„ŒํŠธ (Child.vue)

<template>
  <div class="box">
    <header>
      <slot name="header">๊ธฐ๋ณธ ํ—ค๋”</slot>
    </header>
    <main>
      <slot>๊ธฐ๋ณธ ๋‚ด์šฉ</slot>
    </main>
    <footer>
      <slot name="footer">๊ธฐ๋ณธ ํ‘ธํ„ฐ</slot>
    </footer>
  </div>
</template>

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ (Parent.vue)

<template>
  <Child>
    <template #header>
      <h1>์ปค์Šคํ…€ ํ—ค๋”</h1>
    </template>
    
    <p>๋ฉ”์ธ ์ฝ˜ํ…์ธ </p>

    <template #footer>
      <p>์ปค์Šคํ…€ ํ‘ธํ„ฐ</p>
    </template>
  </Child>
</template>

๐Ÿš€ ๊ฒฐ๊ณผ:

<header>
  <h1>์ปค์Šคํ…€ ํ—ค๋”</h1>
</header>
<main>
  <p>๋ฉ”์ธ ์ฝ˜ํ…์ธ </p>
</main>
<footer>
  <p>์ปค์Šคํ…€ ํ‘ธํ„ฐ</p>
</footer>

๐Ÿ Š #header โ†’ <slot name="header">
๐Ÿ Š #default โ†’ <slot>
๐Ÿ Š #footer โ†’ <slot name="footer">


4๏ธโƒฃ โญScoped Slots (๋ฒ”์œ„๊ฐ€ ์žˆ๋Š” ์Šฌ๋กฏ)

โ—ผ๏ธ ๋ถ€๋ชจ๊ฐ€ ์Šฌ๋กฏ ๋‚ด๋ถ€์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

์ž์‹ ์ปดํฌ๋„ŒํŠธ (Child.vue)

<template>
  <div>
    <slot :text="message"></slot>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const message = ref("์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ „๋‹ฌ๋œ ๋ฉ”์‹œ์ง€");
</script>

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ (Parent.vue)

<template>
  <Child v-slot="{ text }">
    <p>{{ text }}</p>
  </Child>
</template>

<script setup>
import Child from './Child.vue';
</script>

๐Ÿš€ ๊ฒฐ๊ณผ:

<p>์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ „๋‹ฌ๋œ ๋ฉ”์‹œ์ง€</p>

๐Ÿ Š v-slot="{ text }"๋ฅผ ์‚ฌ์šฉํ•ด slot์œผ๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.


5๏ธโƒฃ v-slot์˜ ์ถ•์•ฝ ๋ฌธ๋ฒ•

โ—ผ๏ธ Vue 3์—์„œ๋Š” v-slot์„ #์œผ๋กœ ์ค„์—ฌ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

<Child #default="{ text }">
  <p>{{ text }}</p>
</Child>

์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ์—๋„ ์ ์šฉ ๊ฐ€๋Šฅ

<Child #header>
  <h1>์ปค์Šคํ…€ ํ—ค๋”</h1>
</Child>

๐Ÿ’ฌ ๊ทธ๋Ÿผ <Component /> ์ง์ ‘ ์‚ฌ์šฉ๊ณผ ์ฐจ์ด๊ฐ€ ๋ญ˜๊นŒ?


๐Ÿ”ฅ ๊ฒฐ๋ก 
โœ… ๊ธฐ๋ณธ slot โ†’ ๋ถ€๋ชจ๊ฐ€ ์ „๋‹ฌํ•œ ๋‚ด์šฉ์„ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ํŠน์ • ์œ„์น˜์— ์‚ฝ์ž…
โœ… ์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ โ†’ name์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šฌ๋กฏ ๊ด€๋ฆฌ
โœ… Scoped Slots โ†’ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ชจ์—๊ฒŒ ์ „๋‹ฌ ๊ฐ€๋Šฅ
โœ… v-slot ์ถ•์•ฝ ๋ฌธ๋ฒ• โ†’ #default, #header ๋“ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ


๐Ÿ’ก B2B ํ”„๋กœ์ ํŠธ์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์Šฌ๋กฏ ๋ฐฉ์‹
โ—ป๏ธ ์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ (Named Slots)
โ–ช๏ธ ์ด์œ : B2B ์‹œ์Šคํ…œ์—์„œ๋Š” ๋ ˆ์ด์•„์›ƒ์ด ์ผ์ •ํ•˜์ง€๋งŒ ์ผ๋ถ€๋งŒ ๋‹ค๋ฅด๊ฒŒ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Œ. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ…Œ์ด๋ธ”, ์นด๋“œ, ๋ชจ๋‹ฌ ๊ฐ™์€ UI์—์„œ ํ—ค๋”/๋ฐ”๋””/ํ‘ธํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•ด์„œ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. #header, #footer ๊ฐ™์€ ๊ตฌํš๋ณ„ ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์ง.

โ—ป๏ธ Scoped Slots
โ–ช๏ธ ์ด์œ : ํ…Œ์ด๋ธ”, ๋ฆฌ์ŠคํŠธ, ๋“œ๋กญ๋‹ค์šด ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์ ์œผ๋กœ ํ‘œ์‹œํ•  ๋•Œ ์œ ์šฉํ•จ.
์˜ˆ๋ฅผ ๋“ค์–ด, ์กฐํšŒ ํ™”๋ฉด์—์„œ ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ชจ์—์„œ ๊ฐ€๊ณตํ•˜๊ณ  ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ. ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ชจ์—์„œ ์ „๋‹ฌํ•˜๊ณ , ์ž์‹์—์„œ v-slot์„ ์‚ฌ์šฉํ•ด ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์ข‹์Œ.


โœ… B2B ํ”„๋กœ์ ํŠธ์—์„œ์˜ ์‹ค์ œ ์˜ˆ์ œ
1๏ธโƒฃ ์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ (Named Slots) ๐Ÿ Š ์‚ฌ์šฉ ์˜ˆ์‹œ: ์กฐํšŒ ํ™”๋ฉด์˜ ์นด๋“œํ˜• UI

โ—ผ๏ธ ์กฐํšŒ ํ™”๋ฉด์—์„œ ๊ณ ๊ฐ/๊ณ„์•ฝ ์ •๋ณด๋ฅผ ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•  ๋•Œ ์‚ฌ์šฉ.

CustomerCard.vue (์ž์‹ ์ปดํฌ๋„ŒํŠธ)

<template>
  <div class="card">
    <header><slot name="header">๊ธฐ๋ณธ ์ œ๋ชฉ</slot></header>
    <section><slot>๊ธฐ๋ณธ ๋‚ด์šฉ</slot></section>
    <footer><slot name="footer">๊ธฐ๋ณธ ํ‘ธํ„ฐ</slot></footer>
  </div>
</template>

CustomerList.vue

<template>
  <CustomerCard>
    <template #header>
      <h2>VIP ๊ณ ๊ฐ</h2>
    </template>

    <p>๊ณ ๊ฐ๋ช…: ํ™๊ธธ๋™</p>
    <p>๊ณ„์•ฝ ์ƒํƒœ: ํ™œ์„ฑ</p>

    <template #footer>
      <button @click="viewDetail">์ƒ์„ธ ๋ณด๊ธฐ</button>
    </template>
  </CustomerCard>
</template>

๐Ÿ“Œ ์žฅ์ :
โ–ช๏ธ ๋ ˆ์ด์•„์›ƒ์ด ๊ณ ์ •์ ์ด๋ฉด์„œ๋„, ํ—ค๋”/๋ฐ”๋””/ํ‘ธํ„ฐ๋ฅผ ์œ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ.
โ–ช๏ธ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์นด๋“œ์—์„œ ๋‹ค๋ฅธ ์ฝ˜ํ…์ธ ๋ฅผ ์‰ฝ๊ฒŒ ์ ์šฉ ๊ฐ€๋Šฅ.


2๏ธโƒฃ Scoped Slots (๋ฒ”์œ„๊ฐ€ ์žˆ๋Š” ์Šฌ๋กฏ) ๐Ÿ Š ์‚ฌ์šฉ ์˜ˆ์‹œ: ๊ณ ๊ฐ ๋ชฉ๋ก ํ…Œ์ด๋ธ”

โ—ผ๏ธ v-for๋กœ ๋ฐ˜๋ณต๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋™์ ์œผ๋กœ ํ‘œ์‹œํ•ด์•ผ ํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉ๋จ.
CustomerCard.vue (์ž์‹ ์ปดํฌ๋„ŒํŠธ)

<template>
  <table>
    <thead>
      <tr>
        <th>์ด๋ฆ„</th>
        <th>๊ณ„์•ฝ ์ƒํƒœ</th>
        <th>์ž‘์—…</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="customer in customers" :key="customer.id">
        <td>{{ customer.name }}</td>
        <td>{{ customer.status }}</td>
        <td>
          <slot name="actions" :customer="customer"></slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
import { defineProps } from 'vue';

defineProps({
  customers: Array,
});
</script>

CustomerList.vue (๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ)

<template>
  <CustomerTable :customers="customerList">
    <template #actions="{ customer }">
      <button @click="editCustomer(customer)">์ˆ˜์ •</button>
      <button @click="deleteCustomer(customer)">์‚ญ์ œ</button>
    </template>
  </CustomerTable>
</template>

<script setup>
import CustomerTable from './CustomerTable.vue';

const customerList = [
  { id: 1, name: "ํ™๊ธธ๋™", status: "ํ™œ์„ฑ" },
  { id: 2, name: "๊น€์ฒ ์ˆ˜", status: "๋น„ํ™œ์„ฑ" }
];

const editCustomer = (customer) => {
  console.log("๊ณ ๊ฐ ์ˆ˜์ •:", customer);
};

const deleteCustomer = (customer) => {
  console.log("๊ณ ๊ฐ ์‚ญ์ œ:", customer);
};
</script>

๐Ÿ“Œ ์žฅ์ :
โ–ช๏ธ ํ…Œ์ด๋ธ”์—์„œ ์œ ๋™์ ์ธ ์ž‘์—… ๋ฒ„ํŠผ์„ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ.
โ–ช๏ธ ๋ถ€๋ชจ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต ํ›„ ๋„˜๊ฒจ์ฃผ๊ณ , ์ž์‹์—์„œ ํ™œ์šฉ ๊ฐ€๋Šฅ.
โ–ช๏ธ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ์˜ ๋ฒ„ํŠผ(์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋“ฑ)์„ ์ƒํ™ฉ์— ๋งž๊ฒŒ ๋‹ค๋ฅด๊ฒŒ ํ‘œ์‹œ ๊ฐ€๋Šฅ.

โœ… ๊ฒฐ๋ก : B2B ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ์Šฌ๋กฏ

๐Ÿ“Œ ์ด๋ฆ„ ์žˆ๋Š” ์Šฌ๋กฏ โ†’ ๋ ˆ์ด์•„์›ƒ์ด ์ผ์ •ํ•œ UI
๐Ÿ“Œ Scoped Slots โ†’ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋™์ ์ธ UI

B2B ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์กฐํšŒ์™€ ๊ด€๋ฆฌ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์—, Scoped Slots์ด ํ•„์ˆ˜!
๋˜ํ•œ, ๋ ˆ์ด์•„์›ƒ์ด ๋ฐ˜๋ณต๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์„œ Named Slots๋„ ์ž์ฃผ ์‚ฌ์šฉ๋จ! ๐Ÿš€

0๊ฐœ์˜ ๋Œ“๊ธ€