การใช้งาน Slot และ Dynamic Component เบื้องต้น ตอนที่ 3

บทความ เมื่อไม่กี่สัปดาห์ โดย Ninenik Narkdee
slot vuejs dynamic component

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ slot vuejs dynamic component

ดูแล้ว 172 ครั้ง


เนื้อหานี้จะเป็นตอนสุดท้ายของ คอมโพเนนท์เบื้องต้น หลักๆ แล้ว
เนื้อหาที่กล่าวถึง ก็จะเป็นพื้นฐานที่สำคัญ และสามารถทำให้เข้าใจ
ส่วนต่างๆ ที่จะตามมาเพิ่มเติมได้ เพราะคอมโพเนนท์คือส่วนที่สำคัญ
ใน 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 ที่ค่อนข้างละเอียด เพราะถือว่ามีความสำคัญและมีประโยชน์
สำหรับเนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ