Vue3 中的 Teleport 和 KeepAlive:这两个内置组件到底怎么用

摘要:做后台管理系统的同学一定遇到过这个问题:写了一个弹窗,结果被父级容器的 overflow: hidden 或者 transform: scale() 给整变形了。还有做多步骤表单的时候,用户填到第三步,一不小心点走了标签页,回来发现填的东西全没了。

做后台管理系统的同学一定遇到过这个问题:写了一个弹窗,结果被父级容器的 overflow: hidden 或者 transform: scale() 给整变形了。还有做多步骤表单的时候,用户填到第三步,一不小心点走了标签页,回来发现填的东西全没了。这两个坑,Vue3 的 Teleport 和 KeepAlive 就是来填的。


Teleport:让组件“穿墙”出去

Teleport 这个词就是“瞬间移动”的意思。Vue3 用它解决一个很实际的问题:让组件的 DOM 节点可以渲染到任意位置,但组件的逻辑关系保持不变。

举个例子。你的业务组件 A 嵌套在一个三层深的容器里,这个容器带了 overflow: hidden。你想让 A 里面的弹窗直接挂在 body 下面。以前你得用一些复杂手段,或者把弹窗的状态提到最顶层。现在用 Teleport,一行代码就搞定。

基本写法

<!-- 传送到 body -->
<Teleport to="body">
  <Modal :visible="showModal" />
</Teleport>

<!-- 传送到指定容器,disabled 可以动态开关 -->
<Teleport to="#overlay-container" :disabled="!isMobile">
  <Tooltip />
</Teleport>

Teleport 接受两个参数:to 指定要去哪里(填 CSS 选择器或者 body 关键字),disabled 控制要不要传送。

底层原理

Vue3 的组件实例和 DOM 节点是分开的。Teleport 做的事很简单:在渲染的时候,把子节点的 DOM 操作改到目标容器上。Vue 的响应式系统完全不受影响。你可以在 Teleport 里面正常用 provide/inject,父组件也能通过 ref 拿到子组件实例。

需要注意的地方

Teleport 的目标元素必须在它渲染之前就已经存在。如果目标是动态创建的,要用 nextTick 等它创建好了再渲染。


KeepAlive:让组件“活”下来

如果说 Teleport 是空间上的传送,那 KeepAlive 就是时间上的保存。它让组件在切换后不被销毁,而是缓存起来,下次切回来直接唤醒。

这个在 Tab 切换、路由缓存、多步骤表单里特别好用。用户填了五分钟的表单,因为手滑切到别的页面就全丢了,这种事太让人崩溃了。

生命周期变化

普通组件切换时,会走 unmounted 然后 mounted,状态全丢。被 KeepAlive 包住后,切换时走的是 deactivated 和 activated,组件实例和 DOM 都被缓存,状态还在。

import { onActivated, onDeactivated, ref } from 'vue'

const formData = ref({
  username: '',
  step: 1
});

onActivated(() => {
  console.log('组件复活了,状态还在:', formData.value);
});

onDeactivated(() => {
  console.log('组件被缓存了');
});

控制缓存:include、exclude、max

KeepAlive 给了三个参数来控制缓存:

<KeepAlive
  :include="['UserForm', 'UserProfile']"
  :exclude="['Detail']"
  :max="10"
>
  <component :is="currentComponent" />
</KeepAlive>

include 是只缓存谁,exclude 是不缓存谁,max 是最多缓存多少个。max 这个参数很实用,可以防止缓存太多导致内存占用过高。

注意命名

用 include 和 exclude 的时候,组件必须有 name。在 Vue3.3+ 里用 defineOptions 来声明:

defineOptions({ name: 'UserForm' });


两个一起用:全局弹窗系统

单独用其中一个已经很好了,两个组合起来才是真的大招。常见场景是全局弹窗系统:

  • 弹窗内容需要 Teleport 到 body,不被父级样式影响

  • 弹窗的状态需要 KeepAlive 缓存,切换路由不丢失

  • 用户填了一半的表单,关掉再打开,数据还在

<Teleport to="body">
  <KeepAlive>
    <template v-for="modal in modals" :key="modal.id">
      <component
        :is="modal.component"
        v-if="modal.visible"
        :visible="modal.visible"
      />
    </template>
  </KeepAlive>
</Teleport>


实际用法:多步骤表单加全局确认框

路由级缓存

<!-- App.vue -->
<KeepAlive :include="['OrderConfirm', 'AddressEdit', 'PaymentSelect']">
  <RouterView />
</KeepAlive>

几个建议

  • 不是所有页面都要缓存。列表页、详情页一般不需要,按需来就行

  • 在 onDeactivated 里清理定时器和事件监听,避免内存泄漏

  • Teleport 的目标尽量用一个固定的容器,不要动态创建


常见问题

Teleport 里的组件还能用父组件的 provide 吗?

能。Teleport 只改 DOM 位置,不改组件逻辑树。依赖注入、事件传递、ref 获取都不受影响。

KeepAlive 缓存的组件会释放内存吗?

不主动释放。但设了 max 之后,最久没用过的实例会被销毁,释放内存。

include 和 exclude 支持动态更新吗?

支持。它们是响应式的,可以根据用户操作动态调整。


总结

Teleport 打破 DOM 层级限制,让组件渲染到任意位置,逻辑关系不变。KeepAlive 让组件切换时不销毁,状态保留。两个一起用,全局弹窗、表单缓存、多 Tab 应用都能处理得很干净。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13723