青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件
青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件
- 一、Transition
- `<Transition>` 组件
- 基于 CSS 的过渡效果
- CSS 过渡 class
- 为过渡效果命名
- CSS 的 transition
- CSS 的 animation
- 自定义过渡 class
- 同时使用 transition 和 animation
- 深层级过渡与显式过渡时长
- 性能考量
- JavaScript 钩子
- 可复用过渡效果
- 出现时过渡
- 元素间过渡
- 过渡模式
- 组件间过渡
- 动态过渡
- 使用 Key Attribute 过渡
- 二、TransitionGroup
- TransitionGroup
- 和 `<Transition>` 的区别
- 进入 / 离开动画
- 移动动画
- 渐进延迟列表动画
- 三、KeepAlive
- KeepAlive
- 基本使用
- 包含/排除
- 最大缓存实例数
- 缓存实例的生命周期
- 四、Teleport
- Teleport
- 基本用法
- 搭配组件使用
- 禁用 Teleport
- 多个 Teleport 共享目标
- 延迟解析的 Teleport
- 五、Suspense
- Suspense
- 异步依赖
- `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 @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;
}
<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 name="fade">...
</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 name="slide-fade"><p v-if="show">hello</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 name="bounce"><p v-if="show" style="text-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 -->
<Transitionname="custom-classes"enter-active-class="animate__animated animate__tada"leave-active-class="animate__animated animate__bounceOutRight"
><p v-if="show">hello</p>
</Transition>
同时使用 transition 和 animation
Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 transitionend 或 animationend,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。
然而在某些场景中,你或许想要在同一个元素上同时使用它们两个。举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation 或 transition:
<Transition type="animation">...</Transition>
深层级过渡与显式过渡时长
尽管过渡 class 仅能应用在 <Transition> 的直接子元素上,我们还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果:
<Transition name="nested"><div v-if="show" class="outer"><div class="inner">Hello</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 :duration="550">...</Transition>
如果有必要的话,你也可以用对象的形式传入,分开指定进入和离开所需的时间:
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
性能考量
你可能注意到我们上面例子中展示的动画所用到的 CSS 属性大多是 transform 和 opacity 之类的。用这些属性制作动画非常高效,因为:
- 他们在动画过程中不会影响到 DOM 结构,因此不会每一帧都触发昂贵的 CSS 布局重新计算。
- 大多数的现代浏览器都可以在执行
transform动画时利用 GPU 进行硬件加速。
相比之下,像 height 或者 margin 这样的属性会触发 CSS 布局变动,因此执行它们的动画效果更昂贵,需要谨慎使用。
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) {}
这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果:
<Transition...:css="false"
>...
</Transition>
在有了 :css="false" 后,我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 @enter 和 @leave 钩子来说,回调函数 done 就是必须的。否则,钩子将被同步调用,过渡将立即完成。
这里是使用 GSAP 库执行动画的一个示例,你也可以使用任何你想要的库,比如 Anime.js 或者 Motion One:
可复用过渡效果
得益于 Vue 的组件系统,过渡效果是可以被封装复用的。要创建一个可被复用的过渡,我们需要为 <Transition> 组件创建一个包装组件,并向内传入插槽内容:
<!-- MyTransition.vue -->
<script>
// JavaScript 钩子逻辑...
</script><template><!-- 包装内置的 Transition 组件 --><Transitionname="my-transition"@enter="onEnter"@leave="onLeave"><slot></slot> <!-- 向内传递插槽内容 --></Transition>
</template><style>
/*必要的 CSS...注意:避免在这里使用 <style scoped>因为那不会应用到插槽内容上
*/
</style>
现在 MyTransition 可以在导入后像内置组件那样使用了:
<MyTransition><div v-if="show">Hello</div>
</MyTransition>
出现时过渡
如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 appear prop:
<Transition appear>...
</Transition>
元素间过渡
除了通过 v-if / v-show 切换一个元素,我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可:
<Transition><button v-if="docState === 'saved'">Edit</button><button v-else-if="docState === 'edited'">Save</button><button v-else-if="docState === 'editing'">Cancel</button>
</Transition>
Click to cycle through states:
在演练场中尝试一下
过渡模式
在之前的例子中,进入和离开的元素都是在同时开始动画的,因此我们不得不将它们设为 position: absolute 以避免二者同时存在时出现的布局问题。
然而,很多情况下这可能并不符合需求。我们可能想要先执行离开动画,然后在其完成 之后 再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 <Transition> 传入一个 mode prop 来实现这个行为:
<Transition mode="out-in">...
</Transition>
将之前的例子改为 mode="out-in" 后是这样:
Click to cycle through states:
<Transition> 也支持 mode="in-out",虽然这并不常用。
组件间过渡
<Transition> 也可以作用于动态组件之间的切换:
<Transition name="fade" mode="out-in"><component :is="activeComponent"></component>
</Transition>
动态过渡
<Transition> 的 props(比如 name)也可以是动态的!这让我们可以根据状态变化动态地应用不同类型的过渡:
<Transition :name="transitionName"><!-- ... -->
</Transition>
这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class,然后在它们之间动态切换。
你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。最后,创建动态过渡的终极方式还是创建可复用的过渡组件,并让这些组件根据动态的 props 来改变过渡的效果。掌握了这些技巧后,就真的只有你想不到,没有做不到的了。
使用 Key Attribute 过渡
有时为了触发过渡,你需要强制重新渲染 DOM 元素。
以计数器组件为例:
<script setup>
import { ref } from 'vue';
const count = ref(0);setInterval(() => count.value++, 1000);
</script><template><Transition><span :key="count">{{ count }}</span></Transition>
</template>
如果不使用 key attribute,则只有文本节点会被更新,因此不会发生过渡。但是,有了 key 属性,Vue 就知道在 count 改变时创建一个新的 span 元素,因此 Transition 组件有两个不同的元素在它们之间进行过渡。
二、TransitionGroup
TransitionGroup
<TransitionGroup> 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
和 <Transition> 的区别
<TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
- 默认情况下,它不会渲染一个容器元素。但你可以通过传入
tagprop 来指定一个元素作为容器元素来渲染。 - 过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
- 列表中的每个元素都 必须 有一个独一无二的
keyattribute。 - CSS 过渡 class 会被应用在列表内的元素上,而不是 容器元素上。
当在 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);
}
移动动画
上面的示例有一些明显的缺陷:当某一项被插入或移除时,它周围的元素会立即发生“跳跃”而不是平稳地移动。我们可以通过添加一些额外的 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:
<TransitionGrouptag="ul":css="false"@before-enter="onBeforeEnter"@enter="onEnter"@leave="onLeave"
><liv-for="(item, index) in computedList":key="item.msg":data-index="index">{{ 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 :is="activeComponent" />
默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。
在下面的例子中,你会看到两个有状态的组件——A 有一个计数器,而 B 有一个通过 v-model 同步 input 框输入内容的文字展示。尝试先更改一下任意一个组件的状态,然后切走,再切回来:
Current component: A
Count: 0
你会发现在切回来之后,之前已更改的状态都被重置了。
在切换时创建新的组件实例通常是有意义的,但在这个例子中,我们的确想要组件能在被“切走”的时候保留它们的状态。要解决这个问题,我们可以用 <KeepAlive> 内置组件将这些动态组件包装起来:
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive><component :is="activeComponent" />
</KeepAlive>
现在,在组件切换时状态也能被保留了:
Current component: A
Count: 0
在 DOM 内模板中使用时,它应该被写为 <keep-alive>。
包含/排除
<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 选项。
在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。
最大缓存实例数
我们可以通过传入 max prop 来限制可被缓存的最大组件实例数。 <KeepAlive> 的行为在指定了 max 后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
<KeepAlive :max="10"><component :is="activeComponent" />
</KeepAlive>
缓存实例的生命周期
当一个组件实例从 DOM 上移除但因为被 <KeepAlive> 缓存而仍作为组件树的一部分时,它将变为 不活跃 状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新 被激活。
一个持续存在的组件可以通过 onActivated() 和 onDeactivated() 注册相应的两个状态的生命周期钩子:
<script setup>
import { onActivated, onDeactivated } from 'vue'onActivated(() => {// 调用时机为首次挂载// 以及每次从缓存中被重新插入时
})onDeactivated(() => {// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用
})
</script>
一个持续存在的组件可以通过 activated 和 deactivated 选项来注册相应的两个状态的生命周期钩子:
export default {activated() {// 在首次挂载、// 以及每次从缓存中被重新插入的时候调用},deactivated() {// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用}
}
请注意:
onActivated``activated在组件挂载时也会调用,并且onDeactivated``deactivated在组件卸载时也会调用。- 这两个钩子不仅适用于
<KeepAlive>缓存的根组件,也适用于缓存树中的后代组件。
四、Teleport
Teleport
<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
基本用法
有时我们可能会遇到这样的场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。
这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身是在同一个组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。
试想下面这样的 HTML 结构:
<div class="outer"><h3>Tooltips with Vue 3 Teleport</h3><div><MyModal /></div>
</div>
接下来我们来看看 <MyModal> 的实现:
<script setup>
import { ref } from 'vue'const open = ref(false)
</script><template><button @click="open = true">Open Modal</button><div v-if="open" class="modal"><p>Hello from the modal!</p><button @click="open = false">Close</button></div>
</template><style scoped>
.modal {position: fixed;z-index: 999;top: 20%;left: 50%;width: 300px;margin-left: -150px;
}
</style>
<script>
export default {data() {return {open: false}}
}
</script><template><button @click="open = true">Open Modal</button><div v-if="open" class="modal"><p>Hello from the modal!</p><button @click="open = false">Close</button></div>
</template><style 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样式属性。也就是说如果我们想要用 CSStransform为祖先节点<div class="outer">设置动画,就会不小心破坏模态框的布局!- 这个模态框的
z-index受限于它的容器元素。如果有其他元素与<div class="outer">重叠并有更高的z-index,则它会覆盖住我们的模态框。
<Teleport> 提供了一个更简单的方式来解决此类问题,让我们不需要再顾虑 DOM 结构的问题。让我们用 <Teleport> 改写一下 <MyModal>:
<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 标签下”。
你可以点击下面这个按钮,然后通过浏览器的开发者工具,在 <body> 标签下找到模态框元素:
我们也可以将 <Teleport> 和 <Transition> 结合使用来创建一个带动画的模态框。你可以看看 [这个示例]。
<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport> 之前先挂载该元素。
搭配组件使用
<Teleport> 只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系。也就是说,如果 <Teleport> 包含了一个组件,那么该组件始终和这个使用了 <Teleport> 的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。
这也意味着来自父组件的注入也会按预期工作,子组件将在 Vue Devtools 中嵌套在父级组件下面,而不是放在实际内容移动到的地方。
禁用 Teleport
在某些场景下可能需要视情况禁用 <Teleport>。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 <Teleport> 动态地传入一个 disabled prop 来处理这两种不同情况。
<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>
延迟解析的 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
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()
</script><template>{{ posts }}
</template>
异步组件
异步组件默认就是 “suspensible” 的。这意味着如果组件关系链上有一个 <Suspense>,那么这个异步组件就会被当作这个 <Suspense> 的一个异步依赖。在这种情况下,加载状态是由 <Suspense> 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。
异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制,并让组件始终自己控制其加载状态。
加载中状态
<Suspense> 组件有两个插槽: #default 和 #fallback。两个插槽都只允许 一个 直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
<Suspense><!-- 具有深层异步依赖的组件 --><Dashboard /><!-- 在 #fallback 插槽中显示 “正在加载中” --><template #fallback>Loading...</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-if="Component"><Transition mode="out-in"><KeepAlive><Suspense><!-- 主要内容 --><component :is="Component"></component><!-- 加载中状态 --><template #fallback>正在加载...</template></Suspense></KeepAlive></Transition></template>
</RouterView>
Vue Router 使用动态导入对懒加载组件进行了内置支持。这些与异步组件不同,目前他们不会触发 <Suspense>。但是,它们仍然可以有异步组件作为后代,这些组件可以照常触发 <Suspense>。
嵌套使用
当我们有多个类似于下方的异步组件(常见于嵌套或基于布局的路由)时:
<Suspense><component :is="DynamicAsyncOuter"><component :is="DynamicAsyncInner" /></component>
</Suspense>
<Suspense> 创建了一个边界,它将如预期的那样解析树下的所有异步组件。然而,当我们更改 DynamicAsyncOuter 时, <Suspense> 会正确地等待它,但当我们更改 DynamicAsyncInner 时,嵌套的 DynamicAsyncInner 会呈现为一个空节点,直到它被解析为止(而不是之前的节点或回退插槽)。
为了解决这个问题,我们可以使用嵌套的方法来处理嵌套组件的补丁,就像这样:
<Suspense><component :is="DynamicAsyncOuter"><Suspense suspensible> <!-- 像这样 --><component :is="DynamicAsyncInner" /></Suspense></component>
</Suspense>
如果你不设置 suspensible 属性,内部的 <Suspense> 将被父级 <Suspense> 视为同步组件。这意味着它将会有自己的回退插槽,如果两个 Dynamic 组件同时被修改,则当子 <Suspense> 加载其自己的依赖关系树时,可能会出现空节点和多个修补周期,这可能不是理想情况。设置后,所有异步依赖项处理都会交给父级 <Suspense>(包括发出的事件),而内部 <Suspense> 仅充当依赖项解析和修补的另一个边界。
相关文章:
青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件
青少年编程与数学 02-006 前端开发框架VUE 19课题、内置组件 一、Transition<Transition> 组件基于 CSS 的过渡效果CSS 过渡 class为过渡效果命名CSS 的 transitionCSS 的 animation自定义过渡 class同时使用 transition 和 animation深层级过渡与显式过渡时长性能考量 J…...
腾讯云AI代码助手编程挑战赛 - 使用 JavaScript 构建一个简易日历
功能简介: 动态年份选择:用户可以通过下拉框选择从 2000 年到 2050 年的任意年份。全年日历生成:根据用户选择的年份,动态生成该年份的所有 12 个月份的日历。直观的 UI 设计:使用 CSS 美化日历外观,使日历…...
Xcode 正则表达式实现查找替换
在软件开发过程中,查找和替换文本是一项常见的任务。正则表达式(Regular Expressions)是一种强大的工具,可以帮助我们在复杂的文本中进行精确的匹配和替换。Xcode 作为一款流行的开发工具,提供了对正则表达式的支持。本…...
学习flv.js
前言 flv.js一款使用纯 JavaScript 编写的 HTML5 Flash 视频 (FLV) 播放器,无需 Flash!!!flv.js 的工作原理是将 FLV 文件流转换为 ISO BMFF(碎片 MP4)片段,然后通过Media Source Extensions&l…...
FreePBX 17 on ubuntu24 with Asterisk 20
版本配置: FreePBX 17(最新) Asterisk 20(最新Asterisk 22,但是FreePBX 17最新只支持Asterisk 21,但是21非LTS版本,所以选择Asterisk 20) PHP 8.2 Maria DB (v10.11) Node J…...
【算法】算法大纲
这篇文章介绍计算机算法的各个思维模式。 包括 计数原理、数组、树型结构、链表递归栈、查找排序、管窥算法、图论、贪心法和动态规划、以及概率论:概率分治和机器学习。没有办法逐个说明,算法本身错综复杂,不同的算法对应着不同的实用场景,也需要根据具体情况设计与调整。…...
【MySQL】SQL菜鸟教程(一)
1.常见命令 1.1 总览 命令作用SELECT从数据库中提取数据UPDATE更新数据库中的数据DELETE从数据库中删除数据INSERT INTO向数据库中插入新数据CREATE DATABASE创建新数据库ALTER DATABASE修改数据库CREATE TABLE创建新表ALTER TABLE变更数据表DROP TABLE删除表CREATE INDEX创建…...
安装本地测试安装apache-doris
一、安装前规划 我的服务器是三台麒麟服务器,2台跑不起来,这是我本地的,内存分配的也不多。 fe192.168.1.13 主数据库端口9030访问 8Gbe192.168.1.13内存4G 硬盘50be192.168.1.14内存4G 硬盘50be192.168.1.12内存4G 硬盘5013同时安装的fe和be 。 原理:192.168.1.13 服…...
【Apache Paimon】-- 13 -- 利用 paimon-flink-action 同步 mysql 表数据
利用 Paimon Schema Evolution 核心特性同步变更的 mysql 表结构和数据 1、背景信息 在Paimon 诞生以前,若 mysql/pg 等数据源的表结构发生变化时,我们有几种处理方式 (1)人工通知(比如常规的使用邮件),然后运维人员手动同步到数据仓库中 (2)使用 flink 消费 DDL bi…...
IOS HTTPS代理抓包工具使用教程
打开抓包软件 在设备列表中选择要抓包的 设备,然后选择功能区域中的 HTTPS代理抓包。根据弹出的提示按照配置文件和设置手机代理。如果是本机则会自动配置,只需要按照提醒操作即可。 iOS 抓包准备 通过 USB 将 iOS 设备连接到电脑,设备需解…...
在 Ubuntu 22.04 上从 Wayland 切换到 X11的详细步骤
在 Ubuntu 22.04 上从 Wayland 切换到 X11,步骤其实很简单,主要是在登录界面进行选择。以下是详细的步骤: 步骤 1:退出当前会话 首先,点击屏幕右上角的用户菜单,选择 注销 或 退出,以退出当前…...
【Linux】4.Linux常见指令以及权限理解(2)
文章目录 3. Linux指令3.1 ls指令和rm指令补充3.2 man指令(重要)3.3cp指令(重要)输出重定向3.3.1ubuntu20.04如何安装tree 3.4 mv指令(重要)mv指令更改文件名mv指令更改目录名 如何看待指令指令的重命名3.5…...
ffmpeg aac s16 encode_audio.c
用ffmpeg库时,用代码对pcm内容采用aac编码进行压缩,出现如下错误。 [aac 000002bc5edc6e40] Format aac detected only with low score of 1, misdetection possible! [aac 000002bc5edc8140] Error decoding AAC frame header. [aac 000002bc5edc81…...
vue3监听器
1.侦听数据源类型 watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组 const x ref(0) const y ref(0)// 单个 ref watch(x, (newX) > {console.log(x is ${newX}) …...
03-51单片机定时器和串口通信
一、51单片机定时器 1.定时器介绍 1.1为什么要使用定时器 在前面的学习中,用到了 Delay 函数延时,这里学习定时器以后,就可以通过定时器来完成,当然定时器的功能远不止这些: 51 单片机的定时器既可以定时ÿ…...
系统架构设计师考点—项目管理
一、备考指南 项目管理主要考查的是进度管理、软件配置管理、质量管理、风险管理等相关知识,近几年都没有考查过,但是有可能在案例分析中考查关键路径的技术问题,考生了解为主。 二、重点考点 1、项目的十大管理(速记࿱…...
代码随想录算法训练营第三十二天|509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯
目录 509.斐波那契数 动态规划五部曲: 1.确定dp数组(dp table)以及下标的含义 2.确定递推公式 3.dp数组如何初始化 4.确定遍历顺序 5.举例推导dp数组 70.爬楼梯 动态规划五部曲: 1.确定dp数组(dp table)…...
【2024年华为OD机试】 (A卷,100分)- 总最快检测效率(Java JS PythonC/C++)
一、问题描述 题目描述 在系统、网络均正常的情况下组织核酸采样员和志愿者对人群进行核酸检测筛查。 每名采样员的效率不同,采样效率为 N 人/小时。由于外界变化,采样员的效率会以 M 人/小时为粒度发生变化,M 为采样效率浮动粒度…...
【大数据】Apache Superset:可视化开源架构
Apache Superset是什么 Apache Superset 是一个开源的现代化数据可视化和数据探索平台,主要用于帮助用户以交互式的方式分析和展示数据。有不少丰富的可视化组件,可以将数据从多种数据源(如 SQL 数据库、数据仓库、NoSQL 数据库等࿰…...
LabVIEW调用不定长数组 DLL数组
在使用 LabVIEW 调用 DLL 库函数时,如果函数中的结构体包含不定长数组,直接通过 调用库函数节点(Call Library Function Node) 调用通常会遇到问题。这是因为 LabVIEW 需要与 DLL 中的数据结构完全匹配,而包含不定长数…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
若依登录用户名和密码加密
/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...
