阿里云主机折上折
  • 微信号
Current Site:Index > Template syntax enhancements (Fragments/Teleport/Suspense)

Template syntax enhancements (Fragments/Teleport/Suspense)

Author:Chuan Chen 阅读数:54827人阅读 分类: Vue.js

Template Syntax Enhancements (Fragments/Teleport/Suspense)

Vue 3 introduces several template syntax enhancements, including Fragments, Teleport, and Suspense, which significantly improve the development experience and application performance. These features address pain points in specific scenarios, making component structures more flexible, DOM operations more efficient, and asynchronous handling more elegant.

Fragments

Fragments allow components to return multiple root nodes without wrapping them in additional DOM elements. In Vue 2, each component must have exactly one root node, often leading to unnecessary div nesting.

<template>
  <!-- Vue 2 required this -->
  <div>
    <header></header>
    <main></main>
    <footer></footer>
  </div>
</template>

<!-- Vue 3 allows this -->
<template>
  <header></header>
  <main></main>
  <footer></footer>
</template>

In practical scenarios, Fragments are particularly useful for table and list structures:

<template>
  <table>
    <tr>
      <td>Cell 1</td>
      <td>Cell 2</td>
    </tr>
    <!-- Vue 2 couldn't directly place two tr elements here -->
    <Fragment v-for="item in list" :key="item.id">
      <tr class="header-row">{{ item.title }}</tr>
      <tr class="content-row">{{ item.content }}</tr>
    </Fragment>
  </table>
</template>

Teleport

Teleport provides the ability to "teleport" part of a component's template to another location in the DOM, making it ideal for handling modals, notifications, pop-up menus, and other scenarios that require breaking out of component hierarchy constraints.

Basic usage example:

<template>
  <button @click="showModal = true">Open Modal</button>
  
  <Teleport to="body">
    <div v-if="showModal" class="modal">
      <p>This is a global modal</p>
      <button @click="showModal = false">Close</button>
    </div>
  </Teleport>
</template>

<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>

<style>
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: white;
  padding: 20px;
  z-index: 1000;
}
</style>

Teleport supports multiple target locations and conditional teleportation:

<Teleport :to="mobileLayout ? '#mobile-menu' : '#desktop-menu'">
  <NavigationMenu />
</Teleport>

Suspense (Async Component Handling)

Suspense is a dedicated component for handling async component dependencies, elegantly managing loading and error states. It works particularly well with async components and the Composition API.

Basic structure:

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() =>
  import('./AsyncComponent.vue')
)
</script>

In practical applications, it can be combined with multiple async operations:

<template>
  <Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
    <template #default>
      <div>
        <AsyncUserProfile :user-id="userId" />
        <AsyncUserPosts :user-id="userId" />
      </div>
    </template>
    <template #fallback>
      <Spinner size="large" />
      <p>Loading user data...</p>
    </template>
  </Suspense>
</template>

<script setup>
import { ref } from 'vue'
import Spinner from './Spinner.vue'

const userId = ref(1)

const AsyncUserProfile = defineAsyncComponent({
  loader: () => import('./UserProfile.vue'),
  delay: 200, // Delay before showing loading state
  timeout: 3000 // Timeout duration
})

const AsyncUserPosts = defineAsyncComponent(() =>
  import('./UserPosts.vue')
)

function onPending() {
  console.log('Data loading started')
}
</script>

Combined Usage Example

These three features can work together to create more powerful solutions. For example, a modal with async loading:

<template>
  <button @click="openModal">View Details</button>
  
  <Teleport to="#modal-container">
    <Suspense>
      <template #default>
        <ProductModal 
          v-if="showModal" 
          :product-id="selectedId"
          @close="showModal = false"
        />
      </template>
      <template #fallback>
        <div class="modal-loading">
          <Spinner />
          <p>Loading product details...</p>
        </div>
      </template>
    </Suspense>
  </Teleport>
</template>

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

const showModal = ref(false)
const selectedId = ref(null)

const ProductModal = defineAsyncComponent(() => 
  import('./ProductModal.vue')
)

function openModal(id) {
  selectedId.value = id
  showModal.value = true
}
</script>

Performance Considerations

When using these features, consider the following performance implications:

  1. Fragments reduce unnecessary DOM nodes, improving rendering performance.
  2. Teleport content mounts at the target location, and frequent toggling may cause DOM reflows.
  3. Suspense fallback content should be lightweight to avoid complex computations.
<!-- Optimized Suspense usage -->
<Suspense>
  <template #default>
    <HeavyAsyncComponent />
  </template>
  <template #fallback>
    <!-- Lightweight loading indicator -->
    <div class="skeleton-loader"></div>
  </template>
</Suspense>

<style>
.skeleton-loader {
  width: 100%;
  height: 200px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  to {
    background-position: -200% 0;
  }
}
</style>

Interaction with Other Vue Features

These template enhancements work seamlessly with other Vue features:

  • With v-model:
<Teleport to="#notifications">
  <Suspense>
    <NotificationList v-model:unread="unreadCount" />
  </Suspense>
</Teleport>
  • With provide/inject:
<Suspense>
  <template #default>
    <Fragment>
      <ParentComponent>
        <ChildComponent />
      </ParentComponent>
    </Fragment>
  </template>
</Suspense>
  • With routing:
<router-view v-slot="{ Component }">
  <Suspense>
    <component :is="Component" />
  </Suspense>
</router-view>

Extended Practical Applications

Complex Form Scenario:

<template>
  <Fragment>
    <form-header />
    <Suspense>
      <template #default>
        <form-sections />
      </template>
      <template #fallback>
        <form-skeleton />
      </template>
    </Suspense>
    <form-footer />
  </Fragment>
  
  <Teleport to="#form-sidebar">
    <form-help />
  </Teleport>
</template>

Multi-step Wizard:

<template>
  <div class="wizard-container">
    <wizard-progress />
    
    <Suspense>
      <template #default>
        <Fragment>
          <wizard-step v-if="step === 1" />
          <wizard-step v-if="step === 2" />
          <wizard-step v-if="step === 3" />
        </Fragment>
      </template>
    </Suspense>
    
    <Teleport to="#wizard-actions">
      <wizard-navigation />
    </Teleport>
  </div>
</template>

Debugging Tips

Use these methods to debug these features during development:

  1. Add debug markers to Teleport:
<Teleport to="#target" :disabled="isDebug" v-if="!isDebug">
  <!-- Production content -->
</Teleport>

<Teleport to="#debug-target" :disabled="!isDebug" v-if="isDebug">
  <!-- Debug content -->
  <div style="border: 2px dashed red">
    <slot />
  </div>
</Teleport>
  1. Monitor Suspense states:
<Suspense @pending="logEvent('pending')" 
          @resolve="logEvent('resolve')" 
          @fallback="logEvent('fallback')">
  <!-- Content -->
</Suspense>
  1. Visualize Fragments:
/* Add outline for development */
fragment {
  outline: 1px dotted #ccc;
  display: contents;
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.