Vue系列——第 15 篇:内置组件全家桶——Transition、Teleport、Suspense、KeepAlive

本篇定位 :Vue 提供了一系列内置组件,用于处理常见的 UI 模式和性能优化场景。本篇将系统讲解四个最核心的内置组件------Transition(过渡动画)、Teleport(传送门)、Suspense(异步依赖处理)和 KeepAlive(缓存组件)。读完本篇,你将能够为应用添加流畅的过渡动画、灵活地控制 DOM 渲染位置、优雅地处理异步组件加载,以及智能地缓存组件状态。


一、Transition:过渡与动画

1.1 基本概念

Transition 是 Vue 提供的过渡动画组件 ,它可以在元素或组件的进入、离开和列表变化时自动应用过渡效果。

vue 复制代码
<template>
  <button @click="show = !show">切换</button>
  <Transition>
    <p v-if="show">这是一个带有过渡动画的元素</p>
  </Transition>
</template>

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

<style>
/* 进入/离开的过渡状态 */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

Transition 的核心特征:

  • 当 v-if、v-show 或动态组件切换时自动触发
  • 通过 CSS 类名控制动画过程
  • 支持 JavaScript 钩子函数
  • 支持 CSS 动画库(如 Animate.css)

1.2 过渡类名详解

Vue 在元素过渡过程中会添加/移除以下 6 个 CSS 类:

复制代码
进入阶段:
  v-enter-from    →  v-enter-active  →  v-enter-to
  (起始状态)        (过渡中)           (结束状态)

离开阶段:
  v-leave-from    →  v-leave-active  →  v-leave-to
  (起始状态)        (过渡中)           (结束状态)
类名 时机 说明
v-enter-from 插入前 元素的起始状态(如 opacity: 0)
v-enter-active 插入到过渡结束 定义过渡的持续时间、缓动函数
v-enter-to 插入后 元素的结束状态(如 opacity: 1)
v-leave-from 离开触发时 元素的起始状态
v-leave-active 离开到过渡结束 定义过渡的持续时间、缓动函数
v-leave-to 离开后 元素的结束状态

自定义过渡名称:

vue 复制代码
<template>
  <!-- 使用 name 属性自定义类名前缀 -->
  <Transition name="fade">
    <p v-if="show">内容</p>
  </Transition>
</template>

<style>
/* 类名变为 fade-enter-from、fade-enter-active 等 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

1.3 常见过渡效果

① 淡入淡出:

css 复制代码
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

② 滑动(从上方滑入):

css 复制代码
.slide-enter-active,
.slide-leave-active {
  transition: all 0.4s ease;
}
.slide-enter-from {
  transform: translateY(-30px);
  opacity: 0;
}
.slide-leave-to {
  transform: translateY(30px);
  opacity: 0;
}

③ 缩放:

css 复制代码
.scale-enter-active,
.scale-leave-active {
  transition: all 0.3s ease;
}
.scale-enter-from,
.scale-leave-to {
  transform: scale(0.8);
  opacity: 0;
}

④ 组合动画:

css 复制代码
.combined-enter-active {
  animation: bounceIn 0.6s ease;
}
.combined-leave-active {
  animation: bounceOut 0.4s ease;
}
@keyframes bounceIn {
  0% { transform: scale(0.3); opacity: 0; }
  50% { transform: scale(1.05); opacity: 1; }
  70% { transform: scale(0.9); }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes bounceOut {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.3); opacity: 0; }
}

1.4 使用 CSS 动画库(Animate.css)

bash 复制代码
npm install animate.css
vue 复制代码
<script setup>
import { ref } from 'vue'
import 'animate.css'
const show = ref(true)
</script>

<template>
  <button @click="show = !show">切换</button>
  <Transition
    enter-active-class="animate__animated animate__bounceIn"
    leave-active-class="animate__animated animate__bounceOut"
  >
    <p v-if="show">带有 Animate.css 动画的内容</p>
  </Transition>
</template>

使用自定义属性:

vue 复制代码
<Transition
  enter-active-class="animate__animated animate__fadeInUp"
  leave-active-class="animate__animated animate__fadeOutDown"
  :duration="800"
>
  <div v-if="show">内容</div>
</Transition>

1.5 JavaScript 钩子

使用 JavaScript 钩子可以实现更复杂的动画控制:

vue 复制代码
<template>
  <Transition
    @before-enter="onBeforeEnter"
    @enter="onEnter"
    @after-enter="onAfterEnter"
    @enter-cancelled="onEnterCancelled"
    @before-leave="onBeforeLeave"
    @leave="onLeave"
    @after-leave="onAfterLeave"
    @leave-cancelled="onLeaveCancelled"
  >
    <div v-if="show">内容</div>
  </Transition>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(true)

function onBeforeEnter(el) {
  el.style.opacity = '0'
  el.style.transform = 'translateY(-20px)'
}
function onEnter(el, done) {
  el.animate([
    { opacity: 0, transform: 'translateY(-20px)' },
    { opacity: 1, transform: 'translateY(0)' }
  ], {
    duration: 500,
    easing: 'ease'
  }).finished.then(done)
}
function onAfterEnter(el) {
  console.log('进入完成')
}
// ... 其他钩子
</script>

使用 GSAP 动画库:

vue 复制代码
<script setup>
import { ref } from 'vue'
import gsap from 'gsap'
const show = ref(true)

function onEnter(el, done) {
  gsap.from(el, {
    opacity: 0,
    y: 30,
    duration: 0.6,
    ease: 'power2.out',
    onComplete: done
  })
}
function onLeave(el, done) {
  gsap.to(el, {
    opacity: 0,
    y: -30,
    duration: 0.4,
    ease: 'power2.in',
    onComplete: done
  })
}
</script>

1.6 TransitionGroup:列表过渡

TransitionGroup 用于为列表中的元素添加过渡动画:

vue 复制代码
<script setup>
import { ref } from 'vue'
import { TransitionGroup } from 'vue'

const items = ref([1, 2, 3, 4, 5])
let nextId = 6

function addItem() {
  items.value.push(nextId++)
}
function removeItem(index) {
  items.value.splice(index, 1)
}
function shuffle() {
  items.value.sort(() => Math.random() - 0.5)
}
</script>

<template>
  <div>
    <button @click="addItem">添加</button>
    <button @click="shuffle">打乱</button>
    
    <TransitionGroup name="list" tag="ul" class="list">
      <li v-for="item in items" :key="item" class="list-item">
        {{ item }}
        <button @click="removeItem(items.indexOf(item))">×</button>
      </li>
    </TransitionGroup>
  </div>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from {
  opacity: 0;
  transform: translateX(30px);
}
.list-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}
/* 移动动画(列表重排时) */
.list-move {
  transition: transform 0.5s ease;
}
</style>

二、Teleport:传送门

2.1 基本概念

Teleport 允许将组件的内容传送到 DOM 中的任意位置,而不受组件层级限制。

使用场景:

  • 模态框(Modal)
  • 通知提示(Toast)
  • 下拉菜单
  • 工具提示(Tooltip)
vue 复制代码
<template>
  <button @click="open = true">打开弹窗</button>
  
  <!-- 将弹窗传送到 body 下 -->
  <Teleport to="body">
    <div v-if="open" class="modal-overlay" @click="open = false">
      <div class="modal" @click.stop>
        <h2>这是一个弹窗</h2>
        <p>通过 Teleport 传送到 body 下</p>
        <button @click="open = false">关闭</button>
      </div>
    </div>
  </Teleport>
</template>

2.2 Teleport 的目标选择器

to 属性支持多种选择器:

vue 复制代码
<!-- CSS 选择器 -->
<Teleport to="body">
  <div>传送到 body</div>
</Teleport>

<Teleport to="#app">
  <div>传送到 id="app" 的元素</div>
</Teleport>

<Teleport to=".container">
  <div>传送到 class="container" 的元素</div>
</Teleport>

<!-- 动态目标 -->
<Teleport :to="targetSelector">
  <div>动态传送</div>
</Teleport>

2.3 禁用 Teleport

使用 disabled 属性控制 Teleport 是否生效:

vue 复制代码
<Teleport to="body" :disabled="isMobile">
  <!-- 在移动端不传送,保持组件内 -->
  <div class="modal">内容</div>
</Teleport>

2.4 多个 Teleport 到同一目标

多个 Teleport 传送到同一目标时,内容会按顺序追加:

vue 复制代码
<Teleport to="#app">
  <div>第一个内容</div>
</Teleport>
<Teleport to="#app">
  <div>第二个内容</div>
</Teleport>
<!-- 渲染结果:第一个内容 + 第二个内容 -->

2.5 综合实战:全局通知系统

vue 复制代码
<!-- NotificationContainer.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { eventBus } from './eventBus'

interface Notification {
  id: number
  message: string
  type: 'info' | 'success' | 'warning' | 'error'
  duration: number
}

const notifications = ref<Notification[]>([])
let idCounter = 0

function showNotif(message: string, type: Notification['type'] = 'info', duration = 3000) {
  const id = idCounter++
  notifications.value.push({ id, message, type, duration })
  
  if (duration > 0) {
    setTimeout(() => {
      removeNotif(id)
    }, duration)
  }
}

function removeNotif(id: number) {
  notifications.value = notifications.value.filter(n => n.id !== id)
}

// 监听事件总线
onMounted(() => {
  eventBus.on('notify', showNotif)
})
onUnmounted(() => {
  eventBus.off('notify', showNotif)
})

function getIcon(type: string) {
  const icons = {
    info: 'ℹ️',
    success: '✅',
    warning: '⚠️',
    error: '❌'
  }
  return icons[type as keyof typeof icons] || 'ℹ️'
}

function getColor(type: string) {
  const colors = {
    info: { bg: '#ebf8ff', border: '#bee3f8', text: '#2b6cb0' },
    success: { bg: '#f0fff4', border: '#c6f6d5', text: '#276749' },
    warning: { bg: '#fffff0', border: '#f6e05e', text: '#975a16' },
    error: { bg: '#fff5f5', border: '#fed7d7', text: '#9b2c2c' }
  }
  return colors[type as keyof typeof colors] || colors.info
}
</script>

<template>
  <!-- 使用 Teleport 将通知传送到 body -->
  <Teleport to="body">
    <TransitionGroup name="notification" class="notification-container">
      <div
        v-for="notif in notifications"
        :key="notif.id"
        class="notification"
        :style="{
          background: getColor(notif.type).bg,
          borderColor: getColor(notif.type).border,
          color: getColor(notif.type).text
        }"
      >
        <span class="icon">{{ getIcon(notif.type) }}</span>
        <span class="message">{{ notif.message }}</span>
        <button @click="removeNotif(notif.id)" class="close">×</button>
      </div>
    </TransitionGroup>
  </Teleport>
</template>

<style>
.notification-container {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 400px;
}
.notification {
  padding: 12px 16px;
  border: 2px solid;
  border-radius: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.notification .message { flex: 1; font-size: 14px; }
.notification .close {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  opacity: 0.5;
}
.notification .close:hover { opacity: 1; }

.notification-enter-active,
.notification-leave-active {
  transition: all 0.4s ease;
}
.notification-enter-from {
  opacity: 0;
  transform: translateX(100%) scale(0.8);
}
.notification-leave-to {
  opacity: 0;
  transform: translateX(80%) scale(0.6);
}
</style>

三、Suspense:异步依赖处理

3.1 基本概念

Suspense 是 Vue 3 提供的异步组件加载 内置组件,用于在等待异步组件加载时显示备用内容。

vue 复制代码
<template>
  <Suspense>
    <!-- 异步组件 -->
    <AsyncComponent />
    
    <!-- 加载中显示的内容 -->
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

Suspense 的工作流程:

  1. 渲染时,尝试渲染 #default 插槽中的组件
  2. 如果遇到异步依赖 (async setup 或使用了 Suspense 的内置组件),显示 #fallback 内容
  3. 所有异步依赖完成后,切换显示 #default 内容

3.2 异步组件

vue 复制代码
<!-- AsyncComponent.vue -->
<script setup>
// 使用顶层 await
const data = await fetch('/api/data').then(r => r.json())

// 组件会在数据加载完成后才渲染
console.log('数据已加载:', data)
</script>

<template>
  <div>
    <h2>异步加载的数据</h2>
    <pre>{{ data }}</pre>
  </div>
</template>

3.3 使用 defineAsyncComponent

vue 复制代码
<script setup>
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./AsyncComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000
})
</script>

<template>
  <Suspense>
    <AsyncComponent />
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

3.4 嵌套 Suspense

Suspense 可以嵌套使用,每个层级独立处理:

vue 复制代码
<template>
  <Suspense>
    <ParentComponent />
    <template #fallback>
      <div>父组件加载中...</div>
    </template>
  </Suspense>
</template>

<!-- ParentComponent.vue -->
<template>
  <div>
    <h1>父组件</h1>
    <Suspense>
      <ChildComponent />
      <template #fallback>
        <div>子组件加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

3.5 错误处理

vue 复制代码
<script setup>
import { ref } from 'vue'

const error = ref<Error | null>(null)

function handleError(err: Error) {
  console.error('Suspense 错误:', err)
  error.value = err
}
</script>

<template>
  <div v-if="error" class="error">
    ❌ 加载失败:{{ error.message }}
    <button @click="error = null; $forceUpdate()">重试</button>
  </div>
  
  <Suspense @pending="console.log('pending')" @resolve="console.log('resolve')" @fallback="console.log('fallback')">
    <AsyncComponent />
    <template #fallback>
      <div>⏳ 加载中...</div>
    </template>
  </Suspense>
</template>

四、KeepAlive:缓存组件状态

4.1 基本概念

KeepAlive 用于缓存组件实例,避免组件被销毁和重新创建,从而保留状态和提升性能。

vue 复制代码
<template>
  <KeepAlive>
    <component :is="currentComponent" />
  </KeepAlive>
</template>

使用场景:

  • 路由切换时保留页面状态
  • Tab 切换时保留表单内容
  • 列表详情页切换时保留滚动位置

4.2 配合路由使用

vue 复制代码
<!-- App.vue -->
<template>
  <RouterView v-slot="{ Component }">
    <KeepAlive>
      <component :is="Component" />
    </KeepAlive>
  </RouterView>
</template>

仅缓存特定路由:

vue 复制代码
<script setup>
const route = useRoute()
</script>
<template>
  <RouterView v-slot="{ Component }">
    <KeepAlive :include="['UserList', 'Dashboard']">
      <component :is="Component" :key="route.fullPath" />
    </KeepAlive>
  </RouterView>
</template>

4.3 include / exclude / max

属性 说明 示例
include 需要缓存的组件名(正则、字符串、数组) :include="['Home', 'About']"
exclude 不需要缓存的组件名 :exclude="['Login']"
max 最大缓存实例数量 :max="10"
vue 复制代码
<KeepAlive :include="/^User/" :exclude="['UserDetail']" :max="20">
  <component :is="currentComponent" />
</KeepAlive>

4.4 生命周期钩子:onActivated / onDeactivated

被 KeepAlive 缓存的组件会触发额外的生命周期钩子:

vue 复制代码
<script setup>
import { onActivated, onDeactivated } from 'vue'

// 组件被激活时(从缓存中恢复)
onActivated(() => {
  console.log('组件被激活')
  // 刷新数据、恢复滚动位置等
})

// 组件被停用时(被缓存)
onDeactivated(() => {
  console.log('组件被停用')
  // 保存滚动位置、暂停定时器等
})
</script>

4.5 KeepAlive 与 Transition 的组合

vue 复制代码
<template>
  <RouterView v-slot="{ Component }">
    <Transition name="fade" mode="out-in">
      <KeepAlive>
        <component :is="Component" />
      </KeepAlive>
    </Transition>
  </RouterView>
</template>

五、综合实战:带缓存和过渡的 Tab 切换

让我们综合运用 KeepAlive、Transition 和 Teleport,构建一个完整的 Tab 切换应用:

vue 复制代码
<script setup lang="ts">
import { ref, shallowRef, onActivated, onDeactivated } from 'vue'
import { Transition, KeepAlive, Teleport } from 'vue'

// -------- 组件定义 --------
const Home = {
  name: 'Home',
  template: `
    <div class="tab-content">
      <h2>🏠 首页</h2>
      <p>这是首页内容,缓存状态:{{ counter }}</p>
      <button @click="counter++">点击:{{ counter }}</button>
    </div>
  `,
  setup() {
    const counter = ref(0)
    onActivated(() => console.log('🏠 Home 被激活,counter:', counter.value))
    onDeactivated(() => console.log('🏠 Home 被停用'))
    return { counter }
  }
}

const About = {
  name: 'About',
  template: `
    <div class="tab-content">
      <h2>📖 关于</h2>
      <p>关于页面,缓存状态:{{ count }}</p>
      <button @click="count++">点击:{{ count }}</button>
    </div>
  `,
  setup() {
    const count = ref(0)
    onActivated(() => console.log('📖 About 被激活'))
    onDeactivated(() => console.log('📖 About 被停用'))
    return { count }
  }
}

const Contact = {
  name: 'Contact',
  template: `
    <div class="tab-content">
      <h2>📧 联系方式</h2>
      <form @submit.prevent="submit">
        <input v-model="email" placeholder="输入邮箱" />
        <button type="submit">提交</button>
        <p v-if="submitted">已提交:{{ email }}</p>
      </form>
    </div>
  `,
  setup() {
    const email = ref('')
    const submitted = ref(false)
    const submit = () => {
      submitted.value = true
    }
    return { email, submitted, submit }
  }
}

// -------- 状态 --------
const tabs = [
  { key: 'home', label: '🏠 首页', component: Home },
  { key: 'about', label: '📖 关于', component: About },
  { key: 'contact', label: '📧 联系', component: Contact }
]

const currentTab = ref(tabs[0].key)
const currentComponent = shallowRef(tabs[0].component)

// 切换 Tab
function switchTab(key: string) {
  const tab = tabs.find(t => t.key === key)
  if (tab) {
    currentTab.value = key
    currentComponent.value = tab.component
  }
}

// -------- 通知系统 --------
const notifications = ref<Array<{ id: number; message: string; type: string }>>([])
let notifId = 0

function showNotification(message: string, type = 'info') {
  const id = notifId++
  notifications.value.push({ id, message, type })
  setTimeout(() => {
    notifications.value = notifications.value.filter(n => n.id !== id)
  }, 3000)
}
</script>

<template>
  <div class="app">
    <h1>Vue 内置组件综合演示</h1>
    
    <!-- Tab 切换 -->
    <div class="tabs">
      <button
        v-for="tab in tabs"
        :key="tab.key"
        @click="switchTab(tab.key)"
        :class="['tab-btn', { active: currentTab === tab.key }]"
      >
        {{ tab.label }}
      </button>
    </div>

    <!-- 内容区域:带过渡和缓存 -->
    <div class="content">
      <Transition name="fade" mode="out-in">
        <KeepAlive :include="['Home', 'About', 'Contact']" :max="5">
          <component :is="currentComponent" />
        </KeepAlive>
      </Transition>
    </div>

    <!-- 操作区 -->
    <div class="actions">
      <button @click="showNotification('这是一个通知消息', 'info')">
        ℹ️ 信息通知
      </button>
      <button @click="showNotification('操作成功!', 'success')">
        ✅ 成功通知
      </button>
      <button @click="showNotification('发生错误!', 'error')">
        ❌ 错误通知
      </button>
    </div>

    <!-- 通知系统:使用 Teleport -->
    <Teleport to="body">
      <TransitionGroup name="notification" class="notification-container">
        <div
          v-for="notif in notifications"
          :key="notif.id"
          :class="['notification', notif.type]"
        >
          {{ notif.message }}
          <button @click="notifications = notifications.filter(n => n.id !== notif.id)">
            ×
          </button>
        </div>
      </TransitionGroup>
    </Teleport>
  </div>
</template>

<style>
.app {
  max-width: 800px;
  margin: 20px auto;
  padding: 30px;
  background: white;
  border-radius: 16px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.06);
}
h1 { font-size: 24px; margin-bottom: 24px; }

/* Tab 样式 */
.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 2px solid #e2e8f0;
  margin-bottom: 20px;
}
.tab-btn {
  padding: 10px 20px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 15px;
  color: #718096;
  border-bottom: 3px solid transparent;
  transition: all 0.3s;
}
.tab-btn:hover { color: #2d3748; }
.tab-btn.active {
  color: #4299e1;
  border-bottom-color: #4299e1;
}

/* 内容过渡 */
.fade-enter-active,
.fade-leave-active {
  transition: all 0.3s ease;
}
.fade-enter-from {
  opacity: 0;
  transform: translateY(10px);
}
.fade-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}

.content {
  min-height: 200px;
  padding: 20px;
  background: #f7fafc;
  border-radius: 10px;
}
.tab-content h2 { margin-top: 0; }
.tab-content button {
  padding: 6px 16px;
  background: #4299e1;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

/* 操作区 */
.actions {
  margin-top: 20px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.actions button {
  padding: 8px 16px;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  background: white;
  cursor: pointer;
}
.actions button:hover { background: #f7fafc; }

/* 通知样式 */
.notification-container {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 380px;
}
.notification {
  padding: 12px 16px;
  border-radius: 10px;
  background: white;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border-left: 4px solid #4299e1;
}
.notification.info { border-left-color: #4299e1; }
.notification.success { border-left-color: #48bb78; }
.notification.error { border-left-color: #fc8181; }
.notification button {
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  opacity: 0.5;
}
.notification button:hover { opacity: 1; }

.notification-enter-active,
.notification-leave-active {
  transition: all 0.4s ease;
}
.notification-enter-from {
  opacity: 0;
  transform: translateX(100%) scale(0.8);
}
.notification-leave-to {
  opacity: 0;
  transform: translateX(80%) scale(0.6);
}
</style>

六、本篇小结

内置组件 核心功能 关键用法
Transition 元素/组件的进入/离开动画 CSS 过渡类、JavaScript 钩子、TransitionGroup
Teleport 将内容传送到 DOM 任意位置 to 目标选择器、disabled 禁用
Suspense 异步组件加载的等待状态 配合 async setup、defineAsyncComponent
KeepAlive 缓存组件实例,保留状态 include/exclude/max、onActivated/onDeactivated

七、思考与练习

  1. 基础练习:实现一个带有淡入淡出过渡的模态框组件,使用 Teleport 传送到 body,并带有背景遮罩层。

  2. 实践任务 :构建一个多标签页应用,每个标签页的组件用 KeepAlive 缓存,并在切换时使用 Transition 实现平滑过渡。

  3. 陷阱排查:以下代码有什么问题,如何修正?

    vue 复制代码
    <KeepAlive :exclude="['Home', 'About']">
      <component :is="currentComponent" />
    </KeepAlive>
  4. 深度思考 :Suspense 和 onMounted 中获取数据有什么区别?什么情况下应该使用 Suspense?

  5. 扩展任务:为通知系统添加以下功能:

    • 支持 duration: 0 表示不自动关闭
    • 添加"清空所有"按钮
    • 通知显示进度条,倒计时指示剩余时间

下篇预告

第 16 篇我们将学习自定义指令与过滤器,扩展 Vue 的能力边界:

  • 自定义指令的生命周期钩子
  • 指令的参数、修饰符和值
  • 在组合式 API 中使用指令
  • Vue 3 中过滤器的移除与替代方案

准备好为 Vue 添加你自己的"魔法"!✨

相关推荐
易师傅13 分钟前
技术面试已死?AI 时代,真正拉开程序员差距的是什么?
前端·后端·程序员
jay神26 分钟前
基于深度学习的交通流量预测系统(Web 版)
前端·人工智能·深度学习·yolo·计算机毕业设计
AiMan42 分钟前
别再把秘塔会话逐个点开:这可把秘塔多个会话一次导出?AI导出鸭
前端·后端·面试
AiMan42 分钟前
你好,请问可以一键导出Dola多个对话框内容吗?一键导出一整年哦
前端
Moment2 小时前
写给迷茫的应届生,怎样在 AI 时代争取更多机会
前端·后端·面试
fengci.2 小时前
CTF2MISC中等部分
开发语言·前端·学习
涛涛ing2 小时前
那个PR太干净了,干净到我差点直接点了合并
前端
去伪存真2 小时前
稠密检索负责脑补,稀疏检索负责兜底:拆解 RAG 倒排索引的“查字典”艺术
前端·agent
喵个咪3 小时前
GoWind 拆解:全链 ID 类型体系——int64 定版与前端字符串化契约
前端·后端·go