内置组件无需注册的即可使用,但是使用渲染函数h()`渲染的时候需要显示导入:
import { h, Transition } from 'vue
h(Transition)
Transition
为单个元素或组件提供动画过渡效果的组件。它可以将进入和离开动画
应用到通过默认插槽
传递给它的元素或组件上。
<Transition>
仅支持单个元素或组件
作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。即<Transition>
的过渡效果仅能应用在 <Transition> 的直接子元素
上
进入和离开动画的触发条件:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
<component>
切换的动态组件 - 改变特殊的
key
属性
基本用法
<template>
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
</template>
<style>
/* 配置进入离开过程的过渡效果*/
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
/* 配置进入过程中刚进入时的透明度 和 离开过程中最终离开的透明度*/
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
最后的效果是hello可以缓慢地隐层和显示
Transition添加动画效果的基本原理
当一个 <Transition>
组件中的元素被插入或移除时,会发生下面这些事情:
- Vue 会自动检测目标元素是否
应用了 CSS 过渡或动画
。如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除。 - 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
- 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
基于css的过渡效果
css过渡class
一共有6个应用进入与离开过渡效果的CSS
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 被移除的同时),在过渡或动画完成之后移除。
为过渡效果命名
我们可以为 <Transition>
组件传一个 name
prop 来声明一个过渡效果名:
<Transition name="fade">
</Transition>
对于有过渡效果名的class,对它起作用的过渡class会以其名字
而不是v
作为前缀。上述内容的class就应该是:
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
CSS的transition
<Transition>
一般都会搭配原生 CSS 过渡一起使用,使用原生的css来为<Transition>
的过渡class添加过渡属性。
<template>
<Transition name="slide-fade">
<p v-if="show">hello</p>
</Transition>
</template>
<style>
/*
进入和离开动画可以使用不同
持续时间和速度曲线。
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
CSS的animation
原生 CSS 动画
和 CSS transition
的应用方式基本上是相同的,只有一点不同,那就是 *-enter-from
不是在元素插入后立即移除,而是在一个 animationend 事件触发时被移除。
当 CSS 动画完成时发生 animationend 事件。
<template>
<Transition name="bounce">
<p v-if="show" style="text-align: center;">
Hello here is some bouncy text!
</p>
</Transition>
</template>
<style>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
</style>
同时使用 transition 和 animation — type
如果<Transition>
标签同时有过渡效果和动画效果就需要指定type
属性需要着重关心哪个效果。
type的取值有两个:'transition' | 'animation'
<Transition type="animation">...</Transition>
自定义过渡class
过渡效果的类名可以自定义不一定必须使用vue定义好的类名,此时就需要使用<Transition>
的属性指定自定义的过渡类名:
- enter-from-class : 进入动画的起始状态的类名
- enter-active-class:进入动画的生效状态的类名
- enter-to-class:进入动画的结束状态的类名
- leave-from-class:离开动画的起始状态的类名
- leave-active-class:离开动画的生效状态的类名
- leave-to-class:离开动画的结束状态的类名
深层过渡效果
过渡class只能作用在<Transition>
的直接子元素上,我们可以使用深层级的CSS选择器
在深层级上触发过渡效果。
<template>
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
</template>
<style>
/* 应用于嵌套元素的规则 */
.nested-enter-active .inner,
.nested-leave-active .inner {
transition: all 0.3s ease-in-out;
}
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(30px);
opacity: 0;
}
</style>
但是这会带来一个小问题。默认情况下,<Transition>
组件会通过监听过渡根元素上的第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成。这不好检测,所以我们直接传入 duration
属性来显式指定过渡的持续时间 (以毫秒为单位)。
<Transition :duration="550">...</Transition>
// 也可以以对象的形式单独指明进入和离开的持续时间
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
JavaScript钩子
<Transition>
组件过渡的过程中有很多监听事件,可以通过监听事件在过渡过程中挂载钩子函数。
Transition组件可配置事件
@before-enter
:进入过渡开始之前调用@before-leave
:离开过渡开始之前调用@enter
:进入过渡开始时调用@leave
:离开过渡开始时调用@appear
:开始初次渲染时调用@after-enter
:进入过渡完成时调用@after-leave
:离开过渡完成时调用@after-appear
:结束初次渲染时调用@enter-cancelled
:进入过渡完成之前被取消时调用@leave-cancelled (v-show only)
:离开过渡完成之前被取消时调用@appear-cancelled
:初次渲染完成之前被取消时调用
常见事件配置的钩子
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
function onEnter(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个done是可选参数
done()
}
过渡钩子的参数:有两个参数,第一个参数是DOM元素,第二个参数是done
,用来控制过渡什么时候结束。
过渡钩子的作用:使用js添加元素的过渡效果。
- 如果单纯使用js过渡钩子添加过渡效果最好设置属性
:css="false"
,跳过对css过渡的自动探测提升性能。 - 如果设置了
:css="false"
需要自己全权控制过渡的效果和实现时机,此时对于@enter 和 @leave
钩子来说,回调函数done
就是必须的。如果不设置,过渡立即完成。
Transition组件可配置属性
- name(string):用于自动生成过渡
CSS
的class名 - css(boolean):是否应用css过渡class。默认是true
- type(‘transition’ | ‘animation’):指定要等待的过渡事件类型,来确定过渡结束的时间。默认情况下会自动检测持续时间较长的类型。
- duration(number | { enter: number; leave: number }):显式指定过渡的持续时间。
- mode(‘in-out’ | ‘out-in’ | ‘default’):控制
离开/进入
过渡的时序, 默认情况下是同时的。
同时是指,前一个元素离开的时候,后一个要显示的元素同时开始进入。
‘out-in’ 是指,前一个元素离开之后,后一个元素再开始进入。 - appear(boolean):是否对
初始渲染
使用过渡。默认值是false - enterFromClass(string):指定进入动画的
起始状态
的类名 - enterActiveClass(string):指定进入动画的
生效状态
的类名 - enterToClass(string):进入动画的
结束状态
的类名 - appearFromClass(string):指定初次渲染动画的
起始状态
的类名 - appearActiveClass(string):指定初次渲染动画的
生效状态
的类名 - appearToClass(string):指定初次渲染动画的
结束状态
的类名 - leaveFromClass(string):离开动画的
起始状态
的类名 - leaveActiveClass(string):离开动画的
生效状态
的类名 - leaveToClass(string):离开动画
结束状态
的类名
封装过渡组件
过渡效果也可以封装成可复用的组件,利用<slot>
实现:
MyTransition.vue:
<script>
// JavaScript 钩子逻辑...
</script>
<template>
<!-- 包装内置的 Transition 组件 -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- 向内传递插槽内容 -->
</Transition>
</template>
<style>
/*
必要的 CSS...
注意:避免在这里使用 <style scoped>
因为那不会应用到插槽内容上
*/
</style>
调用该组件:
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>
这样MyTransition
可以在导入后像内置组件那样使用了。
出现时过渡
如果你想在某个节点初次渲染
时应用一个过渡效果,你可以添加 appear
属性:
<Transition appear>
</Transition>
组件间过渡
使用Transition可以实现组件切换时的过渡效果,只需将组件使用 <component :is="组件名"></component>
进行切换即可:
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>
Transition和key属性
如下代码:
<script setup>
import { ref } from 'vue';
const count = ref(0);
setInterval(() => count.value++, 1000);
</script>
<template>
<Transition>
<span>{{ count }}</span>
</Transition>
</template>
虽然渲染的值一直改变,但是是不会有过渡效果的,因为span始终是一个相同的元素,没有发生改变,改变的只是数值,如果为span元素添加key属性为count,就会有过渡效果,这是因为key不一样,告诉vue是不同的span标签,不同的span标签之间的切换是有过渡效果的。
TransitionGroup
<TransitionGroup>
是一个内置组件,用于对 v-for 列表
中的元素或组件的插入、移除和顺序改变
添加动画效果。
和Transition的区别
<TransitionGroup>
支持和 <Transition>
基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
-
添加了tag属性: 默认情况下,它不会渲染一个
容器元素
,但可以通过传入tag
属性 来指定一个元素作为容器元素来渲染,如果未定义,则渲染为片段 fragment 。<TransitionGroup tag="ul" name="slide"> <li v-for="item in items" :key="item.id"> {{ item.text }} </li> </TransitionGroup>
-
添加moveClass属性:用于定义其他相邻元素移动的class样式名,默认是
.v-move
。 -
删除mode属性:过渡模式(即mode属性)在这里不可用,因为我们不再是在互斥的元素之间进行切换。
-
列表中的每个元素都必须有一个独一无二的
key
属性。 -
CSS 过渡 class 会被应用在
列表内的元素上
,而不是容器元素上。
TransitionGroup使用示例
使用<TransitionGroup>
对一个v-for
列表添加进入离开动画:
<template>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
效果:
移动动画
缺点是周围的元素没有平稳地发生变化,某一项元素添加或者删除的时候他周围的元素会立即发生跳跃,而不是平稳地移动,可以使用.list-move
解决这一问题:
.list-move, /* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.list-leave-active {
position: absolute;
}
效果:
延迟加载动画
可以结合JavaScript 钩子监听器 和 GreenSock library
动画来实延迟加载效果:
<template>
<TransitionGroup
tag="ul"
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
>
<li
v-for="(item, index) in computedList"
:key="item.msg"
:data-index="index"
>
{{ item.msg }}
</li>
</TransitionGroup>
</template>
<style>
</style>
<script>
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
})
}
</script>
延迟效果:
KeepAlive
<KeepAlive>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例
。
默认情况下一个组件被替换之后会被销毁,这会导致他丢失其中所有已变换的状态,当这个组件再一次被显示的时候会创建一个只带有初始状态的新实例。使用<KeepAlive>
包裹组件就会将组件进缓存而不是销毁,就会保存组件上一次切换前的状态。
<KeepAlive>
的作用:
<KeepAlive>
包裹动态组件时,会缓存不活跃的组件实例,而不是销毁它们。- 任何时候都只能有
一个活跃组件实例
作为<KeepAlive>
的直接子节点
普通使用方法
只需要使用<KeepAlive>
标签包裹需要切换的组件即可:
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
包含/排除 — include和 exclude属性
<KeepAlive>
默认会缓存内部的所有组件实例
,但我们可以通过 include
和 exclude
来定制该行为。
include
:指定哪些组件需要被缓存,指明组件名
exclude
:指定哪些组件无需缓存,指明组件名
这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式、字符串数组。
如果使用<KeepAlive>
的 include
和 exclude
属性就需要指明组件的name
属性,它会根据组件的 name
选项进行匹配。使用 <script setup>
的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。
<!-- 以英文逗号分隔的字符串 -->
<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>
最大缓实例 — max属性
我们可以通过传入 max
属性来限制可被缓存的最大组件实例数
。如果缓存的实例数量即将超过指定的最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
缓存实例的生命周期
当一个组件实例从 DOM 上移除但因为被 <KeepAlive>
缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载,所以onMounted
钩子只会执行一次。
可以使用 onActivated()
和 onDeactivated()
生命周期钩子来监听组件是否活跃。
并且:
onActivated
在组件挂载时也会调用,onDeactivated
在组件卸载时也会调用。onActivated()
和onDeactivated()
钩子不仅适用于<KeepAlive>
缓存的根组件,也适用于缓存树中的后代组件。
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
})
</script>
Fragment
- 在Vue2中:组件必须有一个根标签
- 在Vue3中:组件可以没有根标签,但内部会将多个标签包含在一个
Fragment虚拟元素
中,该元素不会解析到页面上。
好处:减少标签层级,减小内存占用
Teleport
<Teleport>
是一种能够将我们组件的html模板
移动到组件DOM结构外层位置的技术。
Teleport的使用情况:
一个组件模板的一部分
在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。如模态框的使用,这时就需使用Teleport
进行渲染
基本使用
模态框在组件中定义的局限性:
- 模态框的
position
一般是fixed
,并且我们希望它是相对于浏览器的窗口来实现fxed
定。但是
position: fixed
能够相对于浏览器窗口放置有一个条件:不能有任何祖先元素设置了transform、perspective 或者 filter
样式属性。 - 这个模态框的 z-index 受限于它的容器元素。如果其他元素的
z-index
高于它的容器元素,就会覆盖我们的模态框。
组件内部定义模态框的渲染结果:模态框嵌套在多级子组件中
如果使用Teleport标签
包裹,利用to
属性可以跳转到任意位置。
-
to:to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。
<Teleport to="#some-id" /> <Teleport to=".some-class" /> <Teleport to="[data-teleport]" />
-
<Teleport>
挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由Vue
渲染的,你需要确保在挂载<Teleport>
之前先挂载该元素。
eg:跳转到body中:
<template>
<div>
<button @click="isShow=true">点我弹个窗</button>
<teleport to="body">
<div v-if="isShow" class="mask">
<div v-if="isShow" class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow=false">关闭弹窗</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from "vue"
export default {
name: 'DialogVue',
setup() {
let isShow = ref(false)
return{isShow}
}
}
</script>
<style>
.dialog{
text-align: center;
width: 300px;
height: 150px;
background-color:rgb(11, 59, 11);
position:absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
.mask{
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background-color:rgba(0,0,0,0.5) ;
}
</style>
效果:
和组件一起使用
<Teleport>
只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系
。也就是说,如果 <Teleport>
包含了一个组件,那么该组件始终和这个使用了 <teleport>
的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。
这也意味着来自父组件的注入也会按预期工作,子组件将在 Vue Devtools
中嵌套在父级组件下面,而不是放在实际内容移动到的地方。
禁用Teleport
Teleport
组件可以禁用,设置disabled:true
即可。
使用情况一般是再不同的情况下选择性的使用Teleport
标签,如再移动设备使用Teleport
标签,在桌面端不使用。
<Teleport :disabled="isMobile">
...
</Teleport>
这里的 isMobile 状态可以根据 CSS media query
的不同结果动态地更新。
多Teleport共享目标
多个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>
服务端渲染Teleports
使用服务端渲染Teleports的时候不会将Teleport包含的内容添加到主应用渲染出的字符串中。所以需要我们手动渲染Teleoprts
的内容, Teleport的内容会暴露在服务端渲染上下文对象的teleports
属性中。
const ctx = {}
const html = await renderToString(app, ctx)
console.log(ctx.teleports) // { '#teleported': 'teleported content' }
document.getElementById('#teleported').innerHTML = 'teleported content'
使用服务端渲染的时候最好不要把 Teleport 的目标设为 body
,通常 <body>
会包含其他服务端渲染出来的内容,这会使得 Teleport 无法确定激活的正确起始位置。
Suspense
补充:组件引入
静态引入——import
我们之前都是使用 import Child from './components/Child'
这种方式引入组件的,这种方式叫做静态引入
。
静态引入的特点是所有组件都加载完之后统一显示。
eg:App组件中嵌套着Child子组件,使用静态引入的方式的话,如果child组件没有加载进来,app组件是不会显示在页面上的,所以App组件最终会和Child组件一同渲染到页面上
异步引入—— ()=>import
- Vue2的引入方法:
使用()=>import
的引入方式就是异步引入。
const AsyncComponent = () => import('./MyComponent.vue')
export default {
components: {
'my-component': AsyncComponent
}
}
- Vue3的引入方法:
使用()=>import
和defineAsyncComponent
引入:
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'))
异步引入的特点是按需加载。
eg:App组件中嵌套着Child子组件,使用异步引入的方式的话,如果child组件还不需要加载,但是app组件已经加载好了,那app组件就会先显示在页面上,Child组件什么时候需要什么时候再进行加载展示。
但是由于异步组件在同步组件渲染完成之后再渲染,可能导致页面出现抖动
的现象,为了解决这个现象引入Suspense
。
Suspense
<Suspense>
是一个内置组件,用来在组件树中协调对异步依赖的处理
。<Suspense>
标签在组件树上层等待<Suspense>
标签内多个异步依赖项解析完成之后再统一显示;同时<Suspense>
在等待时可以渲染一个加载状态。通过这样处理防止页面出现抖动。
疑问:如果使用Supense包裹异步组件的加载,最后所有的异步组件还是被同时渲染出来,好像和直接使用同步组件没什么区别,那为什么还要使用异步组件+Suspense的组和?
- 1. 异步组件是按需加载的,不一定是在页面首次渲染就进行加载,即不一定和同步组件同时加载
- 2. 异步组件可以帮助我们进行代码分割和懒加载处理,使浏览器之请求需要的代码,可以进一步提升页面的加载性能。
总结:异步组件还是有一些同步组件无法替代的优点,所以我们使用异步组件+Suspense
Suspense可处理的异步依赖
<Suspense>
可以等待的异步依赖有两种:
-
带有异步 setup() 钩子的组件。这也包含了使用
<script setup>
时有顶层await
表达式的组件。- 异步setup() 钩子的组件:
export default { async setup() { const res = await fetch(...) const posts = await res.json() return { posts } } }
- 异步
<script setup>
使用<script setup>
,顶层await
表达式会自动让该组件成为一个异步依赖:<script setup> const res = await fetch(...) const posts = await res.json() </script> <template> {{ posts }} </template>
- 异步setup() 钩子的组件:
-
异步组件:使用
()=>import()
引入的组件
如果异步组件关系链上有一个<Suspense>
,那么这个异步组件就会被当作这个<Suspense>
的一个异步依赖。在这种情况下,加载状态是由<Suspense>
控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。
异步组件也可以通过在选项中指定suspensible: false
表明不用 Suspense 控制,并让组件始终自己控制其加载状态。
Suspense实现原理
Suspense
的实现原理是通过插槽实现的,<Suspense>
组件有两个插槽:#default
和 #fallback
。一般情况下都将显示#default
中的节点,无法显示#default
中节点内容的时候显示#fallback
中的节点。
<Suspense>
<!-- 默认插槽无需使用 <template #default> 包裹 -->
<Dashboard />
<!-- 在 #fallback 插槽中显示 “Loading...” -->
<template #fallback>
Loading...
</template>
</Suspense>
页面渲染流程:
- 在初始渲染时,
<Suspense>
将在内存中渲染其默认的插槽内容
。如果在这个过程中遇到任何异步依赖,则会进入挂起状态
。在挂起状态期间,展示的是后备内容(#fallback)
。当所有遇到的异步依赖都完成后,<Suspense>
会进入完成状态,并将展示出默认插槽
的内容。 - 在初次渲染时没有遇到异步依赖,
<Suspense>
会直接进入完成状态。 - 进入完成状态后,只有当
默认插槽的根节点被替换
时,<Suspense>
才会回到挂起状态。组件树中新的更深层次的异步依赖
不会造成<Suspense>
回退到挂起状态。 - 发生回退时,后备内容不会立即展示出来。相反,
<Suspense>
在等待新内容和异步依赖完成时,会展示之前#default
插槽的内容。不过这个可以控制设置timeout
属性,当等待渲染新内容耗时超过timeout
后就显示后背内容,如果timeout
设置为0则在替换默认内容时立即显示后备内容。
Suspense的事件
Suspense有三个事件:
- pending 事件是在
<Suspense>
进入挂起状态时触发。 - resolve 事件是在 default 插槽完成获取新内容时触发。
- fallback 事件则是在 fallback 插槽的内容显示时触发。
Suspense使用实例——异步组件
App.vue:
<template>
<div class="app">
<h2>我是app组件</h2>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<h3>加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
// import Child from './components/Child'//静态引入
// defineAsyncComponent定义一个异步组件
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child'))//动态引入(异步引入)
export default {
name: 'AppVue',
components: { Child },
}
</script>
<style>
.app {
background: #333;
padding: 10px;
}
</style>
Suspense使用实例——异步setup()
如果使用了Suspense和异步组件,那么setup也可以返回Promise对象了。
Child.vue:
<template>
<div class="child">
<h2>我是child组件</h2>
{{sum}}
</div>
</template>
<script>
import { ref } from '@vue/reactivity'
export default {
name: 'ChildVue',
setup() {
let sum = ref(0)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({sum})
},1000)
})
}
}
</script>
<style>
.child {
background: rgb(225, 205, 148);
padding: 10px;
}
</style>
App.vue:
<template>
<div class="app">
<h2>我是app组件</h2>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<h3>加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
// import Child from './components/Child'//静态引入
// defineAsyncComponent定义一个异步组件
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child'))//动态引入(异步引入)
export default {
name: 'AppVue',
components: { Child },
}
</script>
<style>
.app {
background: #333;
padding: 10px;
}
</style>
那么setup也可以使用async修饰了,因为await的返回值就是Promise
child.vue
<template>
<div class="child">
<h2>我是child组件</h2>
{{sum}}
</div>
</template>
<script>
import { ref } from '@vue/reactivity'
export default {
name: 'ChildVue',
async setup() {
let sum = ref(0)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({sum})
},1000)
})
return await p;
}
}
</script>
<style>
.child {
background: rgb(225, 205, 148);
padding: 10px;
}
</style>
多个内置的结合使用
我们常常会将 <Suspense> 和 <Transition>、<KeepAlive>
等组件结合。要保证这些组件都能正常工作,嵌套的顺序
非常重要。同时这些组件都通常与 Vue Router 中的 <RouterView>
组件结合使用:
<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>