修改网站主目录的位置,股票网站模板 dedecms,建设个人网站的好处,大连模板网建站青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件 一、TransitionTransition 组件基于 CSS 的过渡效果CSS 过渡 class为过渡效果命名CSS 的 transitionCSS 的 animation自定义过渡 class同时使用 transition 和 animation深层级过渡与显式过渡时长性能考量 J… 青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件 一、TransitionTransition 组件基于 CSS 的过渡效果CSS 过渡 class为过渡效果命名CSS 的 transitionCSS 的 animation自定义过渡 class同时使用 transition 和 animation深层级过渡与显式过渡时长性能考量 JavaScript 钩子可复用过渡效果出现时过渡元素间过渡过渡模式组件间过渡动态过渡使用 Key Attribute 过渡 二、TransitionGroupTransitionGroup和 Transition 的区别进入 / 离开动画移动动画渐进延迟列表动画 三、KeepAliveKeepAlive基本使用包含/排除最大缓存实例数缓存实例的生命周期 四、TeleportTeleport基本用法搭配组件使用禁用 Teleport多个 Teleport 共享目标延迟解析的 Teleport 五、SuspenseSuspense异步依赖async setup()异步组件 加载中状态事件错误处理和其他组件结合嵌套使用 课题摘要:本文介绍了Vue.js中的内置组件包括Transition、TransitionGroup、KeepAlive和Teleport。Transition组件用于在元素或组件进入和离开DOM时应用动画支持CSS过渡和JavaScript钩子。TransitionGroup用于对v-for列表中的元素添加动画。KeepAlive缓存不活跃的组件实例保持其状态。Teleport将组件的模板部分传送到DOM结构外层的位置。这些内置组件提供了强大的工具用于创建复杂的过渡效果、管理组件生命周期和实现内容的动态传送。 一、Transition
Vue 提供了两个内置组件可以帮助你制作基于状态变化的过渡和动画
Transition 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。TransitionGroup 会在一个 v-for 列表中的元素或组件被插入移动或移除时应用动画。我们将在下一章节中介绍。
除了这两个组件我们也可以通过其他技术手段来应用动画比如切换 CSS class 或用状态绑定样式来驱动动画。这些其他的方法会在动画技巧章节中展开。
Transition 组件
Transition 是一个内置组件这意味着它在任意别的组件中都可以被使用无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发
由 v-if 所触发的切换由 v-show 所触发的切换由特殊元素 component 切换的动态组件改变特殊的 key 属性
以下是最基本用法的示例
button clickshow !showToggle/button
Transitionp v-ifshowhello/p
/Transition/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {transition: opacity 0.5s ease;
}.v-enter-from,
.v-leave-to {opacity: 0;
}Transition 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件这个组件必须仅有一个根元素。
当一个 Transition 组件中的元素被插入或移除时会发生下面这些事情
Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是则一些 CSS 过渡 class 会在适当的时机被添加和移除。如果有作为监听器的 JavaScript 钩子这些钩子函数会在适当时机被调用。如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
基于 CSS 的过渡效果
CSS 过渡 class
一共有 6 个应用于进入与离开过渡效果的 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 被移除的同时在过渡或动画完成之后移除。
v-enter-active 和 v-leave-active 给我们提供了为进入和离开动画指定不同速度曲线的能力我们将在下面的小节中看到一个示例。
为过渡效果命名
我们可以给 Transition 组件传一个 name prop 来声明一个过渡效果名
Transition namefade...
/Transition对于一个有名字的过渡效果对它起作用的过渡 class 会以其名字而不是 v 作为前缀。比如上方例子中被应用的 class 将会是 fade-enter-active 而不是 v-enter-active。这个“fade”过渡的 class 应该是这样
.fade-enter-active,
.fade-leave-active {transition: opacity 0.5s ease;
}.fade-enter-from,
.fade-leave-to {opacity: 0;
}CSS 的 transition
Transition 一般都会搭配原生 CSS 过渡一起使用正如你在上面的例子中所看到的那样。这个 transition CSS 属性是一个简写形式使我们可以一次定义一个过渡的各个方面包括需要执行动画的属性、持续时间和速度曲线。
下面是一个更高级的例子它使用了不同的持续时间和速度曲线来过渡多个属性
Transition nameslide-fadep v-ifshowhello/p
/Transition/*进入和离开动画可以使用不同持续时间和速度曲线。
*/
.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;
}CSS 的 animation
原生 CSS 动画和 CSS transition 的应用方式基本上是相同的只有一点不同那就是 *-enter-from 不是在元素插入后立即移除而是在一个 animationend 事件触发时被移除。
对于大多数的 CSS 动画我们可以简单地在 *-enter-active 和 *-leave-active class 下声明它们。下面是一个示例
Transition namebouncep v-ifshow styletext-align: center;Hello here is some bouncy text!/p
/Transition.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);}
}自定义过渡 class
你也可以向 Transition 传递以下的 props 来指定自定义的过渡 class
enter-from-classenter-active-classenter-to-classleave-from-classleave-active-classleave-to-class
你传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用比如 Animate.css
!-- 假设你已经在页面中引入了 Animate.css --
Transitionnamecustom-classesenter-active-classanimate__animated animate__tadaleave-active-classanimate__animated animate__bounceOutRight
p v-ifshowhello/p
/Transition同时使用 transition 和 animation
Vue 需要附加事件监听器以便知道过渡何时结束。可以是 transitionend 或 animationend这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一Vue 可以自动探测到正确的类型。
然而在某些场景中你或许想要在同一个元素上同时使用它们两个。举例来说Vue 触发了一个 CSS 动画同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type prop 来声明告诉 Vue 需要关心哪种类型传入的值是 animation 或 transition
Transition typeanimation.../Transition深层级过渡与显式过渡时长
尽管过渡 class 仅能应用在 Transition 的直接子元素上我们还是可以使用深层级的 CSS 选择器在深层级的元素上触发过渡效果
Transition namenesteddiv v-ifshow classouterdiv classinnerHello/div/div
/Transition/* 应用于嵌套元素的规则 */
.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;
}/* ... 省略了其他必要的 CSS */我们甚至可以在深层元素上添加一个过渡延迟从而创建一个带渐进延迟的动画序列
/* 延迟嵌套元素的进入以获得交错效果 */
.nested-enter-active .inner {transition-delay: 0.25s;
}然而这会带来一个小问题。默认情况下 Transition 组件会通过监听过渡根元素上的 第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中期望的行为应该是等待所有内部元素的过渡完成。
在这种情况下你可以通过向 Transition 组件传入 duration prop 来显式指定过渡的持续时间以毫秒为单位。总持续时间应该匹配延迟加上内部元素的过渡持续时间
Transition :duration550.../Transition如果有必要的话你也可以用对象的形式传入分开指定进入和离开所需的时间
Transition :duration{ enter: 500, leave: 800 }.../Transition性能考量
你可能注意到我们上面例子中展示的动画所用到的 CSS 属性大多是 transform 和 opacity 之类的。用这些属性制作动画非常高效因为
他们在动画过程中不会影响到 DOM 结构因此不会每一帧都触发昂贵的 CSS 布局重新计算。大多数的现代浏览器都可以在执行 transform 动画时利用 GPU 进行硬件加速。
相比之下像 height 或者 margin 这样的属性会触发 CSS 布局变动因此执行它们的动画效果更昂贵需要谨慎使用。
JavaScript 钩子
你可以通过监听 Transition 组件事件的方式在过渡过程中挂上钩子函数
Transitionbefore-enteronBeforeEnterenteronEnterafter-enteronAfterEnterenter-cancelledonEnterCancelledbefore-leaveonBeforeLeaveleaveonLeaveafter-leaveonAfterLeaveleave-cancelledonLeaveCancelled
!-- ... --
/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) {}这些钩子可以与 CSS 过渡或动画结合使用也可以单独使用。
在使用仅由 JavaScript 执行的动画时最好是添加一个 :cssfalse prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外还可以防止 CSS 规则意外地干扰过渡效果
Transition...:cssfalse
...
/Transition在有了 :cssfalse 后我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 enter 和 leave 钩子来说回调函数 done 就是必须的。否则钩子将被同步调用过渡将立即完成。
这里是使用 GSAP 库执行动画的一个示例你也可以使用任何你想要的库比如 Anime.js 或者 Motion One
可复用过渡效果
得益于 Vue 的组件系统过渡效果是可以被封装复用的。要创建一个可被复用的过渡我们需要为 Transition 组件创建一个包装组件并向内传入插槽内容
!-- MyTransition.vue --
script
// JavaScript 钩子逻辑...
/scripttemplate!-- 包装内置的 Transition 组件 --Transitionnamemy-transitionenteronEnterleaveonLeaveslot/slot !-- 向内传递插槽内容 --/Transition
/templatestyle
/*必要的 CSS...注意避免在这里使用 style scoped因为那不会应用到插槽内容上
*/
/style现在 MyTransition 可以在导入后像内置组件那样使用了
MyTransitiondiv v-ifshowHello/div
/MyTransition出现时过渡
如果你想在某个节点初次渲染时应用一个过渡效果你可以添加 appear prop
Transition appear...
/Transition元素间过渡
除了通过 v-if / v-show 切换一个元素我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换只要确保任一时刻只会有一个元素被渲染即可
Transitionbutton v-ifdocState savedEdit/buttonbutton v-else-ifdocState editedSave/buttonbutton v-else-ifdocState editingCancel/button
/TransitionClick to cycle through states:
在演练场中尝试一下
过渡模式
在之前的例子中进入和离开的元素都是在同时开始动画的因此我们不得不将它们设为 position: absolute 以避免二者同时存在时出现的布局问题。
然而很多情况下这可能并不符合需求。我们可能想要先执行离开动画然后在其完成 之后 再执行元素的进入动画。手动编排这样的动画是非常复杂的好在我们可以通过向 Transition 传入一个 mode prop 来实现这个行为
Transition modeout-in...
/Transition将之前的例子改为 modeout-in 后是这样
Click to cycle through states:
Transition 也支持 modein-out虽然这并不常用。
组件间过渡
Transition 也可以作用于动态组件之间的切换
Transition namefade modeout-incomponent :isactiveComponent/component
/Transition动态过渡
Transition 的 props比如 name也可以是动态的这让我们可以根据状态变化动态地应用不同类型的过渡
Transition :nametransitionName!-- ... --
/Transition这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class然后在它们之间动态切换。
你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。最后创建动态过渡的终极方式还是创建可复用的过渡组件并让这些组件根据动态的 props 来改变过渡的效果。掌握了这些技巧后就真的只有你想不到没有做不到的了。
使用 Key Attribute 过渡
有时为了触发过渡你需要强制重新渲染 DOM 元素。
以计数器组件为例
script setup
import { ref } from vue;
const count ref(0);setInterval(() count.value, 1000);
/scripttemplateTransitionspan :keycount{{ count }}/span/Transition
/template如果不使用 key attribute则只有文本节点会被更新因此不会发生过渡。但是有了 key 属性Vue 就知道在 count 改变时创建一个新的 span 元素因此 Transition 组件有两个不同的元素在它们之间进行过渡。
二、TransitionGroup
TransitionGroup
TransitionGroup 是一个内置组件用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
和 Transition 的区别
TransitionGroup 支持和 Transition 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器但有以下几点区别
默认情况下它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。过渡模式在这里不可用因为我们不再是在互斥的元素之间进行切换。列表中的每个元素都 必须 有一个独一无二的 key attribute。CSS 过渡 class 会被应用在列表内的元素上而不是 容器元素上。
当在 DOM 内模板中使用时组件名需要写为 transition-group。
进入 / 离开动画
这里是 TransitionGroup 对一个 v-for 列表添加进入 / 离开动画的示例
TransitionGroup namelist tagulli v-foritem in items :keyitem{{ 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);
}移动动画
上面的示例有一些明显的缺陷当某一项被插入或移除时它周围的元素会立即发生“跳跃”而不是平稳地移动。我们可以通过添加一些额外的 CSS 规则来解决这个问题
.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 钩子中读取元素的 data attribute我们可以实现带渐进延迟的列表动画。首先我们把每一个元素的索引渲染为该元素上的一个 data attribute
TransitionGrouptagul:cssfalsebefore-enteronBeforeEnterenteronEnterleaveonLeave
liv-for(item, index) in computedList:keyitem.msg:data-indexindex{{ item.msg }}/li
/TransitionGroup接着在 JavaScript 钩子中我们基于当前元素的 data attribute 对该元素的进场动画添加一个延迟。以下是一个基于 GSAP library 的动画示例
function onEnter(el, done) {gsap.to(el, {opacity: 1,height: 1.6em,delay: el.dataset.index * 0.15,onComplete: done})
}三、KeepAlive
KeepAlive
KeepAlive 是一个内置组件它的功能是在多个组件间动态切换时缓存被移除的组件实例。
基本使用
在组件基础章节中我们已经介绍了通过特殊的 component 元素来实现动态组件的用法
component :isactiveComponent /默认情况下一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时会创建一个只带有初始状态的新实例。
在下面的例子中你会看到两个有状态的组件——A 有一个计数器而 B 有一个通过 v-model 同步 input 框输入内容的文字展示。尝试先更改一下任意一个组件的状态然后切走再切回来
Current component: A
Count: 0
你会发现在切回来之后之前已更改的状态都被重置了。
在切换时创建新的组件实例通常是有意义的但在这个例子中我们的确想要组件能在被“切走”的时候保留它们的状态。要解决这个问题我们可以用 KeepAlive 内置组件将这些动态组件包装起来
!-- 非活跃的组件将会被缓存 --
KeepAlivecomponent :isactiveComponent /
/KeepAlive现在在组件切换时状态也能被保留了
Current component: A
Count: 0
在 DOM 内模板中使用时它应该被写为 keep-alive。
包含/排除
KeepAlive 默认会缓存内部的所有组件实例但我们可以通过 include 和 exclude prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式或是包含这两种类型的一个数组
!-- 以英文逗号分隔的字符串 --
KeepAlive includea,bcomponent :isview /
/KeepAlive!-- 正则表达式 (需使用 v-bind) --
KeepAlive :include/a|b/component :isview /
/KeepAlive!-- 数组 (需使用 v-bind) --
KeepAlive :include[a, b]component :isview /
/KeepAlive它会根据组件的 name 选项进行匹配所以组件如果想要条件性地被 KeepAlive 缓存就必须显式声明一个 name 选项。
在 3.2.34 或以上的版本中使用 script setup 的单文件组件会自动根据文件名生成对应的 name 选项无需再手动声明。
最大缓存实例数
我们可以通过传入 max prop 来限制可被缓存的最大组件实例数。 KeepAlive 的行为在指定了 max 后类似一个 LRU 缓存如果缓存的实例数量即将超过指定的那个最大数量则最久没有被访问的缓存实例将被销毁以便为新的实例腾出空间。
KeepAlive :max10component :isactiveComponent /
/KeepAlive缓存实例的生命周期
当一个组件实例从 DOM 上移除但因为被 KeepAlive 缓存而仍作为组件树的一部分时它将变为 不活跃 状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时它将重新 被激活。
一个持续存在的组件可以通过 onActivated() 和 onDeactivated() 注册相应的两个状态的生命周期钩子
script setup
import { onActivated, onDeactivated } from vueonActivated(() {// 调用时机为首次挂载// 以及每次从缓存中被重新插入时
})onDeactivated(() {// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用
})
/script一个持续存在的组件可以通过 activated 和 deactivated 选项来注册相应的两个状态的生命周期钩子
export default {activated() {// 在首次挂载、// 以及每次从缓存中被重新插入的时候调用},deactivated() {// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用}
}请注意
onActivatedactivated 在组件挂载时也会调用并且 onDeactivateddeactivated 在组件卸载时也会调用。这两个钩子不仅适用于 KeepAlive 缓存的根组件也适用于缓存树中的后代组件。
四、Teleport
Teleport
Teleport 是一个内置组件它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
基本用法
有时我们可能会遇到这样的场景一个组件模板的一部分在逻辑上从属于该组件但从整个应用视图的角度来看它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。
这类场景最常见的例子就是全屏的模态框。理想情况下我们希望触发模态框的按钮和模态框本身是在同一个组件中因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。
试想下面这样的 HTML 结构
div classouterh3Tooltips with Vue 3 Teleport/h3divMyModal //div
/div接下来我们来看看 MyModal 的实现
script setup
import { ref } from vueconst open ref(false)
/scripttemplatebutton clickopen trueOpen Modal/buttondiv v-ifopen classmodalpHello from the modal!/pbutton clickopen falseClose/button/div
/templatestyle scoped
.modal {position: fixed;z-index: 999;top: 20%;left: 50%;width: 300px;margin-left: -150px;
}
/stylescript
export default {data() {return {open: false}}
}
/scripttemplatebutton clickopen trueOpen Modal/buttondiv v-ifopen classmodalpHello from the modal!/pbutton clickopen falseClose/button/div
/templatestyle scoped
.modal {position: fixed;z-index: 999;top: 20%;left: 50%;width: 300px;margin-left: -150px;
}
/style这个组件中有一个 button 按钮来触发打开模态框和一个 class 名为 .modal 的 div它包含了模态框的内容和一个用来关闭的按钮。
当在初始 HTML 结构中使用这个组件时会有一些潜在的问题
position: fixed 能够相对于浏览器窗口放置有一个条件那就是不能有任何祖先元素设置了 transform、 perspective 或者 filter 样式属性。也就是说如果我们想要用 CSS transform 为祖先节点 div classouter 设置动画就会不小心破坏模态框的布局这个模态框的 z-index 受限于它的容器元素。如果有其他元素与 div classouter 重叠并有更高的 z-index则它会覆盖住我们的模态框。
Teleport 提供了一个更简单的方式来解决此类问题让我们不需要再顾虑 DOM 结构的问题。让我们用 Teleport 改写一下 MyModal
button clickopen trueOpen Modal/buttonTeleport tobodydiv v-ifopen classmodalpHello from the modal!/pbutton clickopen falseClose/button/div
/TeleportTeleport 接收一个 to prop 来指定传送的目标。 to 的值可以是一个 CSS 选择器字符串也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段 传送到 body 标签下”。
你可以点击下面这个按钮然后通过浏览器的开发者工具在 body 标签下找到模态框元素
我们也可以将 Teleport 和 Transition 结合使用来创建一个带动画的模态框。你可以看看 [这个示例]。
Teleport 挂载时传送的 to 目标必须已经存在于 DOM 中。理想情况下这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的你需要确保在挂载 Teleport 之前先挂载该元素。
搭配组件使用
Teleport 只改变了渲染的 DOM 结构它不会影响组件间的逻辑关系。也就是说如果 Teleport 包含了一个组件那么该组件始终和这个使用了 Teleport 的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。
这也意味着来自父组件的注入也会按预期工作子组件将在 Vue Devtools 中嵌套在父级组件下面而不是放在实际内容移动到的地方。
禁用 Teleport
在某些场景下可能需要视情况禁用 Teleport。举例来说我们想要在桌面端将一个组件当做浮层来渲染但在移动端则当作行内组件。我们可以通过对 Teleport 动态地传入一个 disabled prop 来处理这两种不同情况。
Teleport :disabledisMobile...
/Teleport这里的 isMobile 状态可以根据 CSS media query 的不同结果动态地更新。
多个 Teleport 共享目标
一个可重用的模态框组件可能同时存在多个实例。对于此类场景多个 Teleport 组件可以将其内容挂载在同一个目标元素上而顺序就是简单的顺次追加后挂载的将排在目标元素下更后面的位置上。
比如下面这样的用例
Teleport to#modalsdivA/div
/Teleport
Teleport to#modalsdivB/div
/Teleport渲染的结果为
div idmodalsdivA/divdivB/div
/div延迟解析的 Teleport
在 Vue 3.5 及更高版本中我们可以使用 defer prop 推迟 Teleport 的目标解析直到应用的其他部分挂载。这允许 Teleport 将由 Vue 渲染且位于组件树之后部分的容器元素作为目标
Teleport defer to#late-div.../Teleport!-- 稍后出现于模板中的某处 --
div idlate-div/div请注意目标元素必须与 Teleport 在同一个挂载/更新周期内渲染即如果 div 在一秒后才挂载Teleport 仍然会报错。延迟 Teleport 的原理与 mounted 生命周期钩子类似。
五、Suspense
Suspense
实验性功能
Suspense 是一项实验性功能。它不一定会最终成为稳定功能并且在稳定之前相关 API 也可能会发生变化。
Suspense 是一个内置组件用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成并可以在等待时渲染一个加载状态。
异步依赖
要了解 Suspense 所解决的问题和它是如何与异步依赖进行交互的我们需要想象这样一种组件层级结构
Suspense
└─ Dashboard├─ Profile│ └─ FriendStatus组件有异步的 setup()└─ Content├─ ActivityFeed 异步组件└─ Stats异步组件在这个组件树中有多个嵌套组件要渲染出它们首先得解析一些异步资源。如果没有 Suspense则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下我们可能会在页面上看到三个旋转的加载态在不同的时间显示出内容。
有了 Suspense 组件后我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时在顶层展示出加载中或加载失败的状态。
Suspense 可以等待的异步依赖有两种
带有异步 setup() 钩子的组件。这也包含了使用 script setup 时有顶层 await 表达式的组件。异步组件。
async setup()
组合式 API 中组件的 setup() 钩子可以是异步的
export default {async setup() {const res await fetch(...)const posts await res.json()return {posts}}
}如果使用 script setup那么顶层 await 表达式会自动让该组件成为一个异步依赖
script setup
const res await fetch(...)
const posts await res.json()
/scripttemplate{{ posts }}
/template异步组件
异步组件默认就是 “suspensible” 的。这意味着如果组件关系链上有一个 Suspense那么这个异步组件就会被当作这个 Suspense 的一个异步依赖。在这种情况下加载状态是由 Suspense 控制而该组件自己的加载、报错、延时和超时等选项都将被忽略。
异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制并让组件始终自己控制其加载状态。
加载中状态
Suspense 组件有两个插槽 #default 和 #fallback。两个插槽都只允许 一个 直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
Suspense!-- 具有深层异步依赖的组件 --Dashboard /!-- 在 #fallback 插槽中显示 “正在加载中” --template #fallbackLoading.../template
/Suspense在初始渲染时 Suspense 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖则会进入 挂起 状态。在挂起状态期间展示的是后备内容。当所有遇到的异步依赖都完成后 Suspense 会进入 完成 状态并将展示出默认插槽的内容。
如果在初次渲染时没有遇到异步依赖 Suspense 会直接进入完成状态。
进入完成状态后只有当默认插槽的根节点被替换时 Suspense 才会回到挂起状态。组件树中新的更深层次的异步依赖 不会 造成 Suspense 回退到挂起状态。
发生回退时后备内容不会立即展示出来。相反 Suspense 在等待新内容和异步依赖完成时会展示之前 #default 插槽的内容。这个行为可以通过一个 timeout prop 进行配置在等待渲染新内容耗时超过 timeout 之后 Suspense 将会切换为展示后备内容。若 timeout 值为 0 将导致在替换默认内容时立即显示后备内容。
事件
Suspense 组件会触发三个事件 pending、 resolve 和 fallback。 pending 事件是在进入挂起状态时触发。 resolve 事件是在 default 插槽完成获取新内容时触发。 fallback 事件则是在 fallback 插槽的内容显示时触发。
例如可以使用这些事件在加载新组件时在之前的 DOM 最上层显示一个加载指示器。
错误处理
Suspense 组件自身目前还不提供错误处理不过你可以使用 errorCaptured 选项或者 onErrorCaptured() 钩子在使用到 Suspense 的父组件中捕获和处理异步错误。
和其他组件结合
我们常常会将 Suspense 和 Transition、 KeepAlive 等组件结合。要保证这些组件都能正常工作嵌套的顺序非常重要。
另外这些组件都通常与 Vue Router 中的 RouterView 组件结合使用。
下面的示例展示了如何嵌套这些组件使它们都能按照预期的方式运行。若想组合得更简单你也可以删除一些你不需要的组件
RouterView v-slot{ Component }template v-ifComponentTransition modeout-inKeepAliveSuspense!-- 主要内容 --component :isComponent/component!-- 加载中状态 --template #fallback正在加载.../template/Suspense/KeepAlive/Transition/template
/RouterViewVue Router 使用动态导入对懒加载组件进行了内置支持。这些与异步组件不同目前他们不会触发 Suspense。但是它们仍然可以有异步组件作为后代这些组件可以照常触发 Suspense。
嵌套使用
当我们有多个类似于下方的异步组件常见于嵌套或基于布局的路由时
Suspensecomponent :isDynamicAsyncOutercomponent :isDynamicAsyncInner //component
/SuspenseSuspense 创建了一个边界它将如预期的那样解析树下的所有异步组件。然而当我们更改 DynamicAsyncOuter 时 Suspense 会正确地等待它但当我们更改 DynamicAsyncInner 时嵌套的 DynamicAsyncInner 会呈现为一个空节点直到它被解析为止而不是之前的节点或回退插槽。
为了解决这个问题我们可以使用嵌套的方法来处理嵌套组件的补丁就像这样
Suspensecomponent :isDynamicAsyncOuterSuspense suspensible !-- 像这样 --component :isDynamicAsyncInner //Suspense/component
/Suspense如果你不设置 suspensible 属性内部的 Suspense 将被父级 Suspense 视为同步组件。这意味着它将会有自己的回退插槽如果两个 Dynamic 组件同时被修改则当子 Suspense 加载其自己的依赖关系树时可能会出现空节点和多个修补周期这可能不是理想情况。设置后所有异步依赖项处理都会交给父级 Suspense包括发出的事件而内部 Suspense 仅充当依赖项解析和修补的另一个边界。