vue提供了一些内置组件,帮助我们实现一些特定功能,例如,动画、缓存组件实例等,可以根据实际需要去使用。
一、Transition
<Transition>
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
<component>
切换的动态组件 - 改变特殊的
key
属性
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
TIP:<Transition>
仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。
1.CSS 过渡 class
v-enter-from
:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。v-enter-active
:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。v-enter-to
:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是v-enter-from
被移除的同时),在过渡或动画完成之后移除。v-leave-from
:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。v-leave-active
:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。v-leave-to
:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是v-leave-from
被移除的同时),在过渡或动画完成之后移除。
2.为过渡效果命名
<Transition name="fade">
...
</Transition>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
3.自定义过渡 class
你也可以向 <Transition>
传递以下的 props 来指定自定义的过渡 class:
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
你传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如 Animate.css:
<!-- 假设你已经在页面中引入了 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</Transition>
可以通过向 <Transition>
组件传入 duration
prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间:
<Transition :duration="550">...</Transition>
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
4.JavaScript 钩子
你可以通过监听 <Transition>
组件事件的方式在过渡过程中挂上钩子函数:
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}
// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done()
}
// 当进入过渡完成时调用。
function onAfterEnter(el) {}
// 当进入过渡在完成之前被取消时调用
function onEnterCancelled(el) {}
// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}
// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done()
}
// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}
// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false"
prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果:
<Transition
...
:css="false"
>
...
</Transition>
二、TransitionGroup
和 <Transition>
的区别:
<TransitionGroup>
支持和 <Transition>
基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
默认情况下,它不会渲染一个容器元素。但你可以通过传入
tag
prop 来指定一个元素作为容器元素来渲染。过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
列表中的每个元素都必须有一个独一无二的
key
attribute。CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
TIP:当在 DOM 内模板中使用时,组件名需要写为 <transition-group>
。
这里是 <TransitionGroup>
对一个 v-for
列表添加进入 / 离开动画的示例:
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
三、KeepAlive
<KeepAlive>
它的功能是在多个组件间动态切换时缓存被移除的组件实例。
默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。我们想要组件能在被“切走”的时候保留它们的状态。要解决这个问题,我们可以用 <KeepAlive>
内置组件将这些动态组件包装起来:
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
1.包含/排除
<KeepAlive>
默认会缓存内部的所有组件实例,但我们可以通过 include
和 exclude
prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组:
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
它会根据组件的 name 选项进行匹配,所以组件如果想要条件性地被 KeepAlive
缓存,就必须显式声明一个 name
选项。
2.最大缓存实例数
我们可以通过传入 max
prop 来限制可被缓存的最大组件实例数。<KeepAlive>
的行为在指定了 max
后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
3.缓存实例的生命周期
当一个组件实例从 DOM 上移除但因为被 <KeepAlive>
缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新被激活。
一个持续存在的组件可以通过 onActivated() 和 onDeactivated() 注册相应的两个状态的生命周期钩子:
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
})
</script>
四、Teleport
<Teleport>
它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
<Teleport>
接收一个 to
prop 来指定传送的目标。to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body
标签下”。
1.禁用teleport
在某些场景下可能需要视情况禁用 <Teleport>
。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 <Teleport>
动态地传入一个 disabled
prop 来处理这两种不同情况:
<Teleport :disabled="isMobile">
...
</Teleport>
2.多个 Teleport 共享目标
一个可重用的 <Modal>
组件可能同时存在多个实例。对于此类场景,多个 <Teleport>
组件可以将其内容挂载在同一个目标元素上,而顺序就是简单的顺次追加,后挂载的将排在目标元素下更后面的位置上,但都在目标元素中。
比如下面这样的用例:
<Teleport to="#modals">
<div>A</div>
</Teleport>
<Teleport to="#modals">
<div>B</div>
</Teleport>
渲染的结果为:
<div id="modals">
<div>A</div>
<div>B</div>
</div>
3.延迟解析的 Teleport
在 Vue 3.5 及更高版本中,我们可以使用 defer
prop 推迟 Teleport 的目标解析,直到应用的其他部分挂载。这允许 Teleport 将由 Vue 渲染且位于组件树之后部分的容器元素作为目标:
<Teleport defer to="#late-div">...</Teleport>
<!-- 稍后出现于模板中的某处 -->
<div id="late-div"></div>
请注意,目标元素必须与 Teleport 在同一个挂载/更新周期内渲染,即如果 <div>
在一秒后才挂载,Teleport 仍然会报错。延迟 Teleport 的原理与 mounted
生命周期钩子类似。
五、Suspense
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Transition mode="out-in">
<KeepAlive>
<Suspense>
<!-- 主要内容 -->
<component :is="Component"></component>
<!-- 加载中状态 -->
<template #fallback>
正在加载...
</template>
</Suspense>
</KeepAlive>
</Transition>
</template>
</RouterView>
<Suspense>
是一项实验性功能。它不一定会最终成为稳定功能,并且在稳定之前相关 API 也可能会发生变化。
<Suspense>
用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
<Suspense>
组件有两个插槽:#default
和 #fallback
。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认插槽中的节点。否则将显示后备插槽中的节点。
<Suspense>
<!-- 具有深层异步依赖的组件 -->
<Dashboard />
<!-- 在 #fallback 插槽中显示 “正在加载中” -->
<template #fallback>
Loading...
</template>
</Suspense>
我们常常会将 <Suspense>
和 <Transition>、<KeepAlive> 等组件结合。要保证这些组件都能正常工作,嵌套的顺序非常重要。