เนื้อหานี้จะเป็นตอนสุดท้ายของ คอมโพเนนท์เบื้องต้น หลักๆ แล้ว
เนื้อหาที่กล่าวถึง ก็จะเป็นพื้นฐานที่สำคัญ และสามารถทำให้เข้าใจ
ส่วนต่างๆ ที่จะตามมาเพิ่มเติมได้ เพราะคอมโพเนนท์คือส่วนที่สำคัญ
ใน vuejs เนื้อหานี้เราจะมาดูเกี่ยวกับการใช้งาน slot และดูเกี่ยวกับ
การใช้งาน Dynamic Component
Slot คืออะไร
Slots ใน Vue.js เป็นกลไกที่ทำให้เราสามารถใส่เนื้อหาที่กำหนดเองเข้าไปใน component
ได้ โดยไม่ต้องแก้ไข component ทำให้ component มีความยืดหยุ่นมากขึ้น
และสามารถนำมาใช้ซ้ำได้ กล่าวคือเราใช้ <slot> ใน component เพื่อกำหนดพื้นที่สำหรับ
แสดงข้อมูลหรือเนื้อหา เพื่อให้เห็นรูปแบบและการใช้งาน slot จะยกตัวอย่างดังนี้
ไฟล์ FancyButton.vue ใน src > components
<script setup>
import { ref } from 'vue'
</script>
<template>
<button class="fancy-button">
<slot>Default Button Text</slot>
</button>
</template>
<style scoped>
.fancy-button {
background-color: #42b983;
border: none;
color: white;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
border-radius: 5px;
}
.fancy-button:hover {
background-color: #38a172;
}
</style>
พิจารณาในส่วนนี้
<button class="fancy-button">
<slot>Default Button Text</slot><!-- slot outlet -->
</button>
สังเกตว่า ส่วนที่ใช้งาน <slot> จะเป็นส่วนจะถูกแทนที่ด้วยข้อความหรือข้อมูลใหม่ หรือเข้าใจ
อย่างง่ายคือบริเวณที่จะถูกแทนที่ด้วยข้อมูลอื่น ซึ่งเดิมค่าเริ่มต้นที่เรากำหนดใน component
คือ Default Button Text
เราไปดูต่อที่ไฟล์ App.vue เมื่อนำมาใช้งาน จะได้เป็นดังนี้
<script setup>
import { ref } from 'vue'
import FancyButton from './components/FancyButton.vue'
</script>
<template>
<FancyButton>
Click me! <!-- slot content -->
</FancyButton>
<br><br>
<FancyButton>
Save <!-- slot content -->
</FancyButton>
</template>
ผลลัพธ์ที่ได้

จะเห็นว่าส่วนที่เป็นข้อมูลใน slot content หรือข้อมูลสำหรับ slot จะถูกนำไปแทนในที
ส่วนของ <slot> แท็กที่อยู่ใน component นั่นคือทำให้เราสามารถส่งค่าใดๆ ที่ต้องการ
เพื่อไปแสดงยังส่วนนั้นได้ง่ายๆ เพียงเอาเนื้อหามาไว้ในส่วนของ slot content แล้วเนื้อหา
นั้นๆ ก็จะไปแสดงในส่วนของ slot outlet หรือ ทางออกหรือส่วนแสดงของ slot เป็นต้น
ดูรูปด้านล่างประกอบเพิ่มเติม

ซึ่งถ้าสมมติเราไม่ส่งค่าใดๆ เข้าไป ตรงปุ่ม ก็จะเป็นข้อความว่า "Default Button Text"
เช่น สมมติเราใช้เป็น
<template>
<FancyButton>
Click me! <!-- slot content -->
</FancyButton>
<br><br>
<FancyButton>
Save <!-- slot content -->
</FancyButton>
<br><br>
<FancyButton></FancyButton>
<br><br>
<FancyButton />
<br><br>
<fancy-button></fancy-button>
</template>
ผลลัพธ์ก็ได้เป็น

การกำหนดชื่อของ Slot
เราสามารถกำหนดชื่อของ slot ที่ใช้งาน โดยใช้ attribute ที่ชื่อว่า name เป็นเหมือนกับ
การกำหนดไอดี ให้กับ slot นั้น ไว้อ้างอิงเรียกใช้งาน และสำหรับทุก slot ที่ไม่ได้กำหนดชื่อ
จะมีชื่อเป็นค่าเริ่มต้นเป็น default หรือมีค่า name="default" เป็นค่าเริ่มต้นถ้าไม่กำหนด
เพื่อให้เห็นภาพที่ชัดเจนขึ้น เราจะจำลองเหมือนกรณีเราสร้างเลเอาท์เว็บไซต์ ที่จะมีส่วนต่างๆ
เช่น ส่วน header main footer แล้วเราต้องการให้เนื้อหาของแต่ละส่วนเปลี่ยนตามค่า
ที่ถูกส่งเข้ามา เช่น ส่วน header ก็แสดงเนื้อหาส่วนบน footer ก็แสดงส่วนล่าง และ main
ก็แสดงเนื้อหาหลัก แบบนี้เป็นต้น
สร้างไฟล์ BaseLayout.vue ไว้ใน src > components
<script setup>
import { ref } from 'vue'
</script>
<template>
<div class="container">
<header>
<slot name="header"></slot>
<!-- We want header content here -->
</header>
<main>
<slot></slot>
<!-- We want main content here -->
</main>
<footer>
<slot name="footer"></slot>
<!-- We want footer content here -->
</footer>
</div>
</template>
ส่วนของ slot ของ main เราไม่ได้กำหนดชื่อ จะมีชื่อเป็น name="default" อัตโนมัติ
เมื่อเราเรียกใช้งานในไฟล์ App.vue
<script setup>
import { ref } from 'vue'
import BaseLayout from './components/BaseLayout.vue'
</script>
<template>
<BaseLayout>
<template v-slot:header>
<h1>This is header</h1>
<!-- content for the header slot -->
</template>
<template v-slot>
<h3>this is main</h3>
</template>
<template v-slot:footer>
<h5>This is footer</h5>
<!-- content for the header slot -->
</template>
</BaseLayout>
</template>
เราสามารถทำการเรนเดอร์แยกส่วนโดยใช้ v-slot:ชื่อ slot หรือเขียนแบบย่อโดยใช้ # ตาม
ด้วยชื่อของ slot เช่น <template #header> หรือสำหรับ main ที่ไม่ได้กำหนดชื่อ
slot ก็จะเป็นชื่อ default เดิมใช้เป็น <template v-slot> ได้เป็น <template #default>
ดูตัวอย่างผลลัพธ์

รูปภาพอธิบายการทำงาน

การกำหนดเงื่อนไขการแสดง Slot
สมมติว่าในคอมโพเนนท์ เราต้องการให้ส่วนนั้นๆ แสดงหรือไม่ขึ้นอยู่กับการเรียกใช้ชื่อ slot
หรือไม่ ถ้าไม่มีการเรียกใช้ ก็ไม่ต้องแสดงส่วนของ template นั้น โดยวิธีนี้ เราใช้งาน v-if
แล้วตรวจสอบการมีอยู่ของการใช้งาน ชื่อ slot ผ่าน $slots property เช่น สมมติว่า
เราต้องการกำหนดเงื่อนไขว่าในคอมโพเนนท์นี้ ถ้าตัว parent ไม่ได้เรียกใช้งาน footer
ก็ไม่แสดง กล่าวคือ ถ้าเรียกใช้งานชื่อ footer เท่านั้น ส่วนนี้จึงจะแสดง สามารถปรับ
ไฟล์ BaseLayout.vue บางส่วนได้เป็นดังนี้
<script setup>
import { ref } from 'vue'
</script>
<template>
<div class="container">
<header>
<slot name="header"></slot>
<!-- We want header content here -->
</header>
<main>
<slot></slot>
<!-- We want main content here -->
</main>
<footer v-if="$slots.footer">
<slot name="footer"></slot>
<!-- We want footer content here -->
</footer>
</div>
</template>
ส่วนของ v-if="$slots.footer" หมายความว่า ถ้ามีการเรียกใช้ชื่อ slot ที่ชื่อ footer ส่วนนี้
จึงจะแสดง ถ้าไม่มีการเรียกใช้ ส่วนนี้ก็จะไม่แสดง นั่นก็คือส่วนของ <footer> จะไม่แสดง
เราจำลองการไม่เรียกใช้ชื่อ footer ในไฟล์ App.vue เป็นดังนี้
<script setup>
import { ref } from 'vue'
import BaseLayout from './components/BaseLayout.vue'
</script>
<template>
<BaseLayout>
<template v-slot:header>
<h1>This is header</h1>
<!-- content for the header slot -->
</template>
<template v-slot>
<h3>this is main</h3>
</template>
<template v-slot:notfooter>
<h5>This is footer</h5>
<!-- content for the header slot -->
</template>
</BaseLayout>
</template>
สังเกตว่า v-slot ตัวสุดท้าย เราเรียกใชังานเป็น notfooter หรือก็คือไม่มีการใช้งาน
footer ผลลัพธ์ที่ได้ก็คือจะไม่แสดงส่วนของ footer ตามรูปตัวอย่างด้านล่าง

สังเกตว่าเนื่องจากไม่มีการเรียกใช้งานชื่อ slot ที่ชื่อว่า footer แต่ไปเรียกใช้ชื่อ notfooter
ซึ่งไม่มีอยู่หรือไม่ได้กำหนด ทำให้ในส่วนของการเรนเดอร์ จะไม่มี <footer> แสดง แสดงเฉพาะ
ส่วนของ <header> และ <main>
เราสามารถกำหนดชื่อการเรียกใช้งาน slot name แบบ dynamic หรืออ้างอิงค่าตัวแปรได้
โดยวิธีก็คล้ายกับการใช้งาน attribute แบบ dynamic ที่เคยอธิบายมาก่อนหน้าแล้ว นั่นคือ
ให้ใช้เครื่องปีกกาสีเหลี่ยม แล้วกำหนดเป็นชื่อตัวแปร สำหรับใช้เป็นชื่อของ slot ที่จะเรียกใช้งาน
ตูตัวอย่างการใช้งาน
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- with shorthand -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
การเรียกใช้งาน Slot propterty จาก Parents
ปกติตามที่เราเข้าใจแต่ต้นคือ เราจะไม่สามารถเรียกใช้งาน property หรือ state ของ child
ที่เป็น component ได้ แต่จริงๆ แล้วเราสามารถเรียกใช้งานได้ผ่าน โดยตัว component จะ
ส่งผ่าน attrbute และตัว parent จะเรียกใช้งานผ่าน v-slot directive เรากลับมาที่ตัวอย่าง
ปุ่ม FancyButton ดูตัวอย่างดังนี้
<script setup>
import { ref } from 'vue'
const default_text = ref('Button')
</script>
<template>
<button class="fancy-button">
<slot :text="default_text">Default Button Text</slot>
</button>
</template>
ในไฟล์คอมโพเนนท์ เรากำหนด state ให้ชื่อว่า default_text มีค่าเป็น 'Button' แล้วกำหนด
ในส่วนของ attribute ของ slot ใช้ชื่อว่า text ซึ่ง text ก็คือชื่อที่เราใช้สำหรับอ้างอิงเรียกใช้
งานค่าตัวแปร default_text ที่เป็น state ของคอมโพเนนท์ เราสามารถกำหนด attribute
ใดๆ ก็ได้นอกจาก text ยกเว้นคำว่า name จะเป็นชื่อสงวนไว้กำหนดชื่อ slot
เมื่อเรากำหนด text เป็น property ขอเรียกย่อๆ ว่า props ของ slot แล้ว ต่อไปก็ทำการ
เรียกใช้งานในส่วนของ parent
ไฟล์ App.vue
<script setup>
import { ref } from 'vue'
import FancyButton from './components/FancyButton.vue'
</script>
<template>
<FancyButton v-slot="slotProps">
{{ slotProps.text }}
</FancyButton>
<br><br>
<FancyButton></FancyButton>
</template>
ผลลัพธ์ที่ได้

เราทดสอบแสดง 2 อัน โดยอันแรกมีการใช้งาน v-slot และกำหนดค่าโดยใช้ชื่อว่า slotProps
v-slot="slotProps" โดยชื่อนี้เรากำหนดเป็นอะไรก็ได้ แต่ต้องให้สอดคล้องกับที่กำลังใช้งาน
เพื่อจะได้เข้าใจง่าย จากนั้น เราก็สามารถเข้าถึง props ของ slot ที่ชื่อว่า text ได้ โดย text
จะดึงค่า state ของคอมโพเนนท์มาใช้อีกที ทำให้เราสามารถใช้คำว่า 'Button' มาใช้งานในส่วน
ของ Parent ได้ผ่านค่า {{ slotProps.text }}
ในขณะที่ปุ่มตัวอย่างที่ 2 เราไม่ได้กำหนดค่าอะไร ปุ่มก็จะเป็นค่าตามเดิม
ถ้าสมมติเราเพิ่มชื่อของ slot เข้าไป เช่นใช้ชื่อว่า button
<template>
<button class="fancy-button">
<slot name="button" :text="default_text">Default Button Text</slot>
</button>
</template>
ในส่วนของ parent เราก็ต้องกำหนดชื่อด้วย
<FancyButton v-slot="slotProps">
{{ slotProps.text }}
</FancyButton>
// หรือเขียนแบบย่อ
<FancyButton #default="slotProps">
{{ slotProps.text }}
</FancyButton>
// ก็เปลี่ยนเป็นแบบมีชื่อ
<FancyButton v-slot:button="slotProps">
{{ slotProps.text }}
</FancyButton>
// หรือเขียนแบบย่อ
<FancyButton #button="slotProps">
{{ slotProps.text }}
</FancyButton>
// เราสามารถเรียกใช้ props แบบ destructuring เพื่อให้เข้าถึงข้อมูลได้ง่ายได้
// ทำให้เวลาเรียกใช้งานก็จะสะดวกขึ้น
<FancyButton #button="{ text }">
{{ text }}
</FancyButton>
การใช้งาน Dynamic components
Dynamic components ใน Vue.js เป็นวิธีที่ช่วยให้เราสามารถเปลี่ยนแปลงคอมโพเนนต์
ที่แสดงผลใน template ได้แบบไดนามิก (dynamic) โดยไม่ต้องกำหนดล่วงหน้าใน HTML
ซึ่งช่วยให้การพัฒนามีความยืดหยุ่นและสามารถตอบสนองต่อ event ได้ง่ายขึ้น สามารถทำ
ได้โดยใช้ <component> และ :is
Vue.js มีแท็ก <component> ที่สามารถใช้เพื่อแสดงผลคอมโพเนนต์แบบไดนามิกโดยใช้
คุณสมบัติ :is เพื่อระบุชื่อของคอมโพเนนต์ที่ต้องการแสดงผล
สมมติเราสร้างคอมโพเนนท์ A และ คอมโพเนนท์ B ดังนี้
ComponentA.vue กับ ComponentB.vue ใน src > components
<template>
<div>Component A</div>
</template>
และ
<template>
<div>Component B</div>
</template>
จากนั้นในไฟล์ parents หรือ App.vue เรียกใช้เป็นดังนี้
<script setup>
import { ref } from 'vue'
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
const current = ref(0)
const components = [
ComponentA,ComponentB
]
function toggleComponent() {
current.value = current.value == 0 ? 1 : 0
}
</script>
<template>
<div>
<component :is="components[current]"></component>
<button @click="toggleComponent">Toggle Component</button>
</div>
</template>
ผลลัพธ์ที่ได้

เมื่อเราคลิกที่ปุ่ม จะเป็นการเปลียนค่า current ซึ่งเป็นค่า index หรือ key ของ components
ที่เรากำหนดเป้น array ค่าจะสลับไปมาระหว่าง 0 กับ 1 ทำให้ component จะสลับการแสดง
ตามเงื่อนไขของการใช้งาน :is="components[current]" นั่นคือ นำ component ที่ตรง
กับเงื่อนไขค่า components[current] เท่านั้นมาแสดง

รูปแบบการใช้งานลักษณะนี้ สามารถไปประยุกต์ใช้งานกับการกำหนด tab หรือ การกำหนดการ
แสดงส่วนของเนื้อหาได้ ชื่อ เราต้องการให้ส่วนอื่นๆ แสดงค่าเดิม แต่ต้องการให้เฉพาะส่วนนี้เปลี่ยน
แปลงค่าก็ใช้ <component> กับ :is ตามรูปแบบตัวอย่างข้างต้นได้
การใช้งานคอมโพเนนต์แบบไดนามิกควรใช้ในกรณีที่จำเป็น เพื่อไม่ให้โค้ดซับซ้อนเกินไป
Dynamic components เป็นฟีเจอร์ที่มีประโยชน์ในการสร้าง UI ที่ยืดหยุ่นและสามารถปรับ
เปลี่ยนได้ง่ายตามความต้องการของแอปพลิเคชัน
เนื้อหาเกี่ยวกับ Component เบื้องต้นตอนที่ 3 ก็ขอจบไว้เพียงเท่านี้ เนื้อหานี้อาจจะมี
การอธิบายเกี่ยวกับ slot ที่ค่อนข้างละเอียด เพราะถือว่ามีความสำคัญและมีประโยชน์
สำหรับเนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม