Template syntax enhancements (Fragments/Teleport/Suspense)
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:
- Fragments reduce unnecessary DOM nodes, improving rendering performance.
- Teleport content mounts at the target location, and frequent toggling may cause DOM reflows.
- 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:
- 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>
- Monitor Suspense states:
<Suspense @pending="logEvent('pending')"
@resolve="logEvent('resolve')"
@fallback="logEvent('fallback')">
<!-- Content -->
</Suspense>
- Visualize Fragments:
/* Add outline for development */
fragment {
outline: 1px dotted #ccc;
display: contents;
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:setup函数与生命周期变化
下一篇:自定义渲染器API