Vue 3 Teleport และ Suspense: รูปแบบขั้นสูงและคำถามสัมภาษณ์ 2026

เรียนรู้รูปแบบขั้นสูงของ Vue 3 Teleport และ Suspense สำหรับการสัมภาษณ์ developer ระดับ senior คู่มือฉบับสมบูรณ์พร้อมตัวอย่างโค้ดและ best practices 2026

Vue 3 Teleport และ Suspense: รูปแบบขั้นสูงและคำถามสัมภาษณ์ 2026

Vue 3 Teleport และ Suspense แก้ไขความท้าทายในการ render สองประเภทที่แตกต่างกัน: การวาง component นอก hierarchy ของ DOM และการประสานงาน dependency แบบ async ทั้งสองเป็น component ในตัวที่ไม่ต้อง import และทั้งสองมักปรากฏในการสัมภาษณ์ developer Vue ระดับ senior

อัปเดต Vue 3.5 Teleport

Vue 3.5 เพิ่ม prop defer ให้กับ Teleport ซึ่งอนุญาตให้ target ถูก render ในภายหลังใน component tree สิ่งนี้แก้ไขปัญหา timing SSR ที่พบบ่อยเมื่อ target ของ portal ไม่มีอยู่ในเวลา mount

พื้นฐาน Teleport และ Prop defer

Teleport ย้ายเนื้อหาที่ render แล้วไปยังตำแหน่ง DOM อื่นในขณะที่ยังคงรักษา component tree ของ Vue Component ยังคงเป็น child เชิงตรรกะของ parent: props, events และ provide/inject ทั้งหมดทำงานปกติ Vue DevTools แสดง component ที่ถูก teleport ซ้อนอยู่ภายใต้ parent ไม่ใช่ภายใต้ element target

vue
<!-- ModalTrigger.vue -->
<script setup>
import { ref } from 'vue'

const isOpen = ref(false)
</script>

<template>
  <button @click="isOpen = true">Open Modal</button>

  <Teleport to="#modal-root">
    <div v-if="isOpen" class="modal-overlay" @click.self="isOpen = false">
      <div class="modal-content">
        <slot />
        <button @click="isOpen = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>

Prop to รับ string CSS selector หรือการอ้างอิง element DOM เมื่อ target ไม่มีอยู่ในเวลา mount Teleport จะส่ง warning และไม่ render อะไรเลย สิ่งนี้สร้างปัญหากับ SSR และ layout แบบไดนามิก

Prop defer (Vue 3.5+)

Prop defer บอก Teleport ให้รอจนกว่าส่วนอื่นของแอปพลิเคชันจะ mount ก่อนที่จะ resolve target สิ่งนี้ทำให้สามารถ teleport ไปยัง element ที่ render ในภายหลังใน component เดียวกันหรือใน component พี่น้อง

vue
<!-- App.vue -->
<template>
  <Teleport defer to="#dynamic-target">
    <NotificationBanner />
  </Teleport>

  <!-- Target ถูก render หลังจาก Teleport -->
  <div id="dynamic-target"></div>
</template>

หากไม่มี defer โค้ดนี้จะล้มเหลวเพราะ #dynamic-target ไม่มีอยู่เมื่อ Teleport mount ด้วย defer Vue จะ resolve target หลังจากทุก component ใน tick เดียวกันได้ mount แล้ว Target ยังต้อง render ในรอบ mount หรือ update เดียวกัน

หลาย Teleport และ Conditional Rendering

หลาย component Teleport สามารถกำหนดเป้าหมายไปยัง element เดียวกัน เนื้อหาถูกเพิ่มตามลำดับการประกาศ:

vue
<template>
  <Teleport to="#notifications">
    <Toast message="First" />
  </Teleport>

  <Teleport to="#notifications">
    <Toast message="Second" />
  </Teleport>
</template>

<!-- ผลลัพธ์ใน #notifications:
  <div>First</div>
  <div>Second</div>
-->

Prop disabled ควบคุมว่าจะเกิด teleportation หรือไม่ เมื่อ disabled เนื้อหาจะ render inline Pattern นี้จัดการ layout ที่ตอบสนองซึ่ง modal ควร overlay บน desktop แต่ render inline บน mobile:

vue
<script setup>
import { useMediaQuery } from '@vueuse/core'

const isMobile = useMediaQuery('(max-width: 768px)')
</script>

<template>
  <Teleport to="body" :disabled="isMobile">
    <MobileDrawer />
  </Teleport>
</template>

Suspense สำหรับการประสานงาน Component Async

Suspense ประสานงาน dependency async ทั่วทั้ง component tree เมื่อ component ลูกหลานใดมี dependency async ที่ยังไม่ resolve Suspense จะแสดงเนื้อหา slot fallback เมื่อทุก dependency resolve แล้ว Suspense จะสลับไปที่ slot default

สถานะทดลอง

Suspense ยังคงเป็นฟีเจอร์ทดลองใน Vue 3.5 API อาจเปลี่ยนแปลงก่อนที่จะถึงสถานะเสถียร การใช้งานใน production ต้องยอมรับความเสี่ยงนี้

vue
<!-- Dashboard.vue -->
<template>
  <Suspense>
    <template #default>
      <DashboardContent />
    </template>

    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

Suspense ติดตาม dependency async สองประเภท: component ที่มี async setup() และ async component ที่สร้างด้วย defineAsyncComponent

Async Setup ด้วย Top-Level Await

Vue 3 script setup รองรับ top-level await component ใดก็ตามที่ใช้ top-level await จะกลายเป็น dependency async ที่ Suspense สามารถติดตามได้:

vue
<!-- UserProfile.vue -->
<script setup>
const response = await fetch('/api/user/profile')
const user = await response.json()
</script>

<template>
  <div class="profile">
    <h1>{{ user.name }}</h1>
    <p>{{ user.email }}</p>
  </div>
</template>

Component นี้ไม่สามารถ render ได้จนกว่า fetch จะเสร็จสมบูรณ์ Suspense boundary ของ parent จะแสดง fallback จนกว่าจะ resolve หากไม่มี Suspense boundary component จะไม่ render

Nested Suspense ด้วย Prop suspensible

แอปพลิเคชันที่ซับซ้อนมักมีหลาย async boundary Vue 3.3 แนะนำ prop suspensible เพื่อควบคุมวิธีที่ component Suspense ที่ซ้อนกันโต้ตอบกับ parent

vue
<!-- PageLayout.vue -->
<template>
  <Suspense>
    <template #default>
      <header>
        <AsyncNavigation />
      </header>

      <main>
        <!-- Inner Suspense ด้วย fallback ของตัวเอง -->
        <Suspense suspensible>
          <template #default>
            <AsyncContent />
          </template>
          <template #fallback>
            <ContentSkeleton />
          </template>
        </Suspense>
      </main>
    </template>

    <template #fallback>
      <PageSkeleton />
    </template>
  </Suspense>
</template>

เมื่อตั้งค่า suspensible Suspense ภายในจะลงทะเบียนกับ Suspense boundary ของ parent Parent จะรอทั้ง AsyncNavigation และ AsyncContent ให้ resolve ก่อนที่จะซ่อน fallback Suspense ภายในยังคงทำหน้าที่เป็น boundary ท้องถิ่น: หาก AsyncContent ใช้เวลานานกว่า ContentSkeleton จะแสดงในขณะที่ AsyncNavigation ยังคงมองเห็นได้

หากไม่มี suspensible Suspense ภายในจะทำงานอย่างอิสระ Parent จะแสดง fallback เฉพาะเมื่อ AsyncNavigation กำลังโหลด โดยไม่สนใจ AsyncContent เลย

พร้อมที่จะพิชิตการสัมภาษณ์ Vue.js / Nuxt.js แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

การรวม Teleport, Suspense และ Transitions

แอปพลิเคชันจริงรวม pattern เหล่านี้เข้าด้วยกัน Modal ที่โหลดเนื้อหาแบบ async ได้รับประโยชน์จากทั้งสาม:

vue
<!-- AsyncModal.vue -->
<script setup>
import { ref, defineAsyncComponent } from 'vue'

const isOpen = ref(false)

const AsyncModalContent = defineAsyncComponent({
  loader: () => import('./ModalContent.vue'),
  loadingComponent: () => import('./ModalSkeleton.vue'),
  delay: 200,
  timeout: 10000
})
</script>

<template>
  <button @click="isOpen = true">Open</button>

  <Teleport to="body">
    <Transition name="modal">
      <div v-if="isOpen" class="modal-wrapper">
        <Suspense>
          <template #default>
            <AsyncModalContent @close="isOpen = false" />
          </template>
          <template #fallback>
            <ModalSkeleton />
          </template>
        </Suspense>
      </div>
    </Transition>
  </Teleport>
</template>

<style>
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.3s ease;
}

.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}
</style>

ลำดับการซ้อนมีความสำคัญ: Teleport ครอบ Transition ซึ่งครอบ element แบบมีเงื่อนไข ซึ่งมี Suspense สิ่งนี้ทำให้ transition ทำ animation กับ modal ทั้งหมดรวมถึง loading state

การจัดการ Error ด้วย Suspense

Suspense ไม่จัดการ error Dependency async ที่ล้มเหลวจะ reject promise และเผยแพร่ error ขึ้นไปใน component tree Lifecycle hook onErrorCaptured จับ error เหล่านี้ที่ boundary:

vue
<!-- ErrorBoundary.vue -->
<script setup>
import { ref, onErrorCaptured } from 'vue'

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  return false // ป้องกันการเผยแพร่
})
</script>

<template>
  <div v-if="error" class="error-state">
    <p>Failed to load: {{ error.message }}</p>
    <button @click="error = null">Retry</button>
  </div>

  <Suspense v-else>
    <template #default>
      <slot />
    </template>
    <template #fallback>
      <slot name="loading" />
    </template>
  </Suspense>
</template>

Pattern นี้ครอบ component tree async ใดก็ตามด้วยทั้ง loading และ error state ปุ่ม retry จะล้าง error ทำให้ Vue render ใหม่และลองการดำเนินการ async อีกครั้ง

คำถามสัมภาษณ์เกี่ยวกับ Teleport และ Suspense

ตำแหน่ง Vue senior ทดสอบ pattern เหล่านี้มากขึ้น ต่อไปนี้คือคำถามทั่วไปและสิ่งที่แยกคำตอบที่แข็งแกร่ง:

Q: เมื่อใดควรใช้ Teleport แทน CSS positioning?

คำตอบที่อ่อน: "เมื่อ z-index ไม่ทำงาน"

คำตอบที่แข็งแกร่ง: Teleport แก้ไขปัญหา hierarchy DOM ที่ CSS ไม่สามารถแก้ไขได้ Modal ภายใน container ที่มี overflow: hidden หรือ transform สร้าง stacking context ใหม่ ทำลาย position: fixed Teleport ย้าย modal ไปที่ body หลุดพ้นจากข้อจำกัดเหล่านี้ในขณะที่ยังคงรักษา component tree เชิงตรรกะของ Vue

Q: Suspense แตกต่างจาก loading state ในแต่ละ component อย่างไร?

คำตอบที่อ่อน: "Suspense สะอาดกว่า"

คำตอบที่แข็งแกร่ง: Suspense ประสานงานหลาย dependency async สิบ component ที่มี loading state แต่ละตัวจะแสดงสิบ spinner ในเวลาที่แตกต่างกัน Suspense แสดง spinner หนึ่งตัวจนกว่าทั้งสิบจะ resolve แล้วแสดงทั้งหมดพร้อมกัน สิ่งนี้สร้างประสิทธิภาพที่รับรู้ได้ราบรื่นกว่าและโค้ด component ที่ง่ายกว่าเนื่องจาก component async ไม่ต้องจัดการ loading state ของตัวเอง

Q: เกิดอะไรขึ้นเมื่อ target ของ Teleport ไม่มีอยู่?

เนื้อหาไม่ render และ Vue บันทึก warning Prop defer ของ Vue 3.5 แก้ไขสิ่งนี้โดยรอให้ component อื่นๆ mount ก่อน หากไม่มี defer ให้แน่ใจว่า target มีอยู่ใน index.html หรือใน component parent ที่ mount ก่อน children

Q: Suspense ทำงานกับ Options API ได้หรือไม่?

ได้ Component ใดก็ตามที่มี async setup() จะ trigger Suspense โดยไม่คำนึงว่าใช้ Composition API หรือ Options API ที่อื่น ฟังก์ชัน setup คือ async boundary ไม่ใช่ style ของ component

Teleport และ Suspense ใน Nuxt 3

Nuxt 3 ขยาย pattern เหล่านี้ด้วยการพิจารณา SSR เอกสาร Nuxt เกี่ยวกับ SSR ครอบคลุม timing ของ hydration

สำหรับ Teleport Nuxt ต้องการให้ target มีอยู่ก่อน hydration เพิ่ม target ของ portal ไปที่ app.vue หรือใช้ prop defer:

vue
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>

  <!-- Target ของ portal สำหรับ SSR -->
  <div id="modal-root"></div>
  <div id="toast-root"></div>
</template>

สำหรับ Suspense useFetch และ useAsyncData ของ Nuxt รวมเข้าโดยอัตโนมัติ Component หน้าที่ใช้ composable เหล่านี้ทำงานกับ Suspense ในตัวของ Nuxt โดยไม่ต้องกำหนดค่าด้วยตนเอง ดู module Vue composables ของ SharpSkill สำหรับการเตรียมสัมภาษณ์ที่เกี่ยวข้อง

Pattern Production สำหรับ Vue 3.5

  • Target ของ Teleport ใน index.html: สร้าง target ของ portal ที่เสถียรซึ่งมีอยู่ก่อนที่โค้ด Vue ใดๆ จะทำงาน สิ่งนี้หลีกเลี่ยงปัญหา timing โดยสิ้นเชิง
  • Suspense boundary ที่ละเอียด: หนึ่ง Suspense ต่อหนึ่งหน่วยการโหลดที่เป็นอิสระ Dashboard ที่มี widget แยกกันได้รับประโยชน์จาก Suspense boundary แยกเพื่อให้ widget ที่เร็วปรากฏทันที
  • Error boundary เหนือ Suspense: ครอบ Suspense ด้วยการจัดการ error เสมอ Network failure และ API error ต้องไม่ทำให้ tree ทั้งหมด crash
  • Timing ของ transition: เมื่อรวม Suspense กับ Transition ให้ตั้ง mode="out-in" เพื่อป้องกันการทับซ้อนระหว่างการเปลี่ยนแปลง state

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Vue.js / Nuxt.js เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 28 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง