详解Vue3 Teleport 的实践及原理


Posted in Vue.js onDecember 02, 2020

Vue3 的组合式 API 以及基于 Proxy 响应式原理已经有很多文章介绍过了,除了这些比较亮眼的更新,Vue3 还新增了一个内置组件: Teleport 。这个组件的作用主要用来将模板内的 DOM 元素移动到其他位置。

使用场景

业务开发的过程中,我们经常会封装一些常用的组件,例如 Modal 组件。相信大家在使用 Modal 组件的过程中,经常会遇到一个问题,那就是 Modal 的定位问题。

话不多说,我们先写一个简单的 Modal 组件。

<!-- Modal.vue -->
<style lang="scss">
.modal {
 &__mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.5);
 }
 &__main {
  margin: 0 auto;
  margin-bottom: 5%;
  margin-top: 20%;
  width: 500px;
  background: #fff;
  border-radius: 8px;
 }
 /* 省略部分样式 */
}
</style>
<template>
 <div class="modal__mask">
  <div class="modal__main">
   <div class="modal__header">
    <h3 class="modal__title">弹窗标题</h3>
    <span class="modal__close">x</span>
   </div>
   <div class="modal__content">
    弹窗文本内容
   </div>
   <div class="modal__footer">
    <button>取消</button>
    <button>确认</button>
   </div>
  </div>
 </div>
</template>

<script>
export default {
 setup() {
  return {};
 },
};
</script>

然后我们在页面中引入 Modal 组件。

<!-- App.vue -->
<style lang="scss">
.container {
 height: 80vh;
 margin: 50px;
 overflow: hidden;
}
</style>
<template>
 <div class="container">
  <Modal />
 </div>
</template>

<script>
export default {
 components: {
  Modal,
 },
 setup() {
  return {};
 }
};
</script>

详解Vue3 Teleport 的实践及原理

如上图所示, div.container 下弹窗组件正常展示。使用 fixed 进行布局的元素,在一般情况下会相对于屏幕视窗来进行定位,但是如果父元素的 transform , perspectivefilter 属性不为 none 时, fixed 元素就会相对于父元素来进行定位。

我们只需要把 .container 类的 transform 稍作修改,弹窗组件的定位就会错乱。

<style lang="scss">
.container {
 height: 80vh;
 margin: 50px;
 overflow: hidden;
 transform: translateZ(0);
}
</style>

详解Vue3 Teleport 的实践及原理

这个时候,使用 Teleport 组件就能解决这个问题了。

Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。 -- Vue 官方文档

我们只需要将弹窗内容放入 Teleport 内,并设置 to 属性为 body ,表示弹窗组件每次渲染都会做为 body 的子级,这样之前的问题就能得到解决。

<template>
 <teleport to="body">
  <div class="modal__mask">
   <div class="modal__main">
    ...
   </div>
  </div>
 </teleport>
</template>

可以在 https://codesandbox.io/embed/vue-modal-h5g8y 查看代码。

详解Vue3 Teleport 的实践及原理

源码解析

我们可以先写一个简单的模板,然后看看 Teleport 组件经过模板编译后,生成的代码。

Vue.createApp({
 template: `
  <Teleport to="body">
   <div> teleport to body </div> 
  </Teleport>
 `
})

详解Vue3 Teleport 的实践及原理

简化后代码:

function render(_ctx, _cache) {
 with (_ctx) {
  const { createVNode, openBlock, createBlock, Teleport } = Vue
  return (openBlock(), createBlock(Teleport, { to: "body" }, [
   createVNode("div", null, " teleport to body ", -1 /* HOISTED */)
  ]))
 }
}

可以看到 Teleport 组件通过 createBlock 进行创建。

// packages/runtime-core/src/renderer.ts
export function createBlock(
  type, props, children, patchFlag
) {
 const vnode = createVNode(
  type,
  props,
  children,
  patchFlag
 )
 // ... 省略部分逻辑
 return vnode
}

export function createVNode(
 type, props, children, patchFlag
) {
 // class & style normalization.
 if (props) {
  // ...
 }

 // encode the vnode type information into a bitmap
 const shapeFlag = isString(type)
  ? ShapeFlags.ELEMENT
  : __FEATURE_SUSPENSE__ && isSuspense(type)
   ? ShapeFlags.SUSPENSE
   : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
     ? ShapeFlags.STATEFUL_COMPONENT
     : isFunction(type)
      ? ShapeFlags.FUNCTIONAL_COMPONENT
      : 0

 const vnode: VNode = {
  type,
  props,
  shapeFlag,
  patchFlag,
  key: props && normalizeKey(props),
  ref: props && normalizeRef(props),
 }

 return vnode
}

// packages/runtime-core/src/components/Teleport.ts
export const isTeleport = type => type.__isTeleport
export const Teleport = {
 __isTeleport: true,
 process() {}
}

传入 createBlock 的第一个参数为 Teleport ,最后得到的 vnode 中会有一个 shapeFlag 属性,该属性用来表示 vnode 的类型。 isTeleport(type) 得到的结果为 true ,所以 shapeFlag 属性最后的值为 ShapeFlags.TELEPORT1 << 6 )。

// packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
 ELEMENT = 1,
 FUNCTIONAL_COMPONENT = 1 << 1,
 STATEFUL_COMPONENT = 1 << 2,
 TEXT_CHILDREN = 1 << 3,
 ARRAY_CHILDREN = 1 << 4,
 SLOTS_CHILDREN = 1 << 5,
 TELEPORT = 1 << 6,
 SUSPENSE = 1 << 7,
 COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
 COMPONENT_KEPT_ALIVE = 1 << 9
}

在组件的 render 节点,会依据 typeshapeFlag 走不同的逻辑。

// packages/runtime-core/src/renderer.ts
const render = (vnode, container) => {
 if (vnode == null) {
  // 当前组件为空,则将组件销毁
  if (container._vnode) {
   unmount(container._vnode, null, null, true)
  }
 } else {
  // 新建或者更新组件
  // container._vnode 是之前已创建组件的缓存
  patch(container._vnode || null, vnode, container)
 }
 container._vnode = vnode
}

// patch 是表示补丁,用于 vnode 的创建、更新、销毁
const patch = (n1, n2, container) => {
 // 如果新旧节点的类型不一致,则将旧节点销毁
 if (n1 && !isSameVNodeType(n1, n2)) {
  unmount(n1)
 }
 const { type, ref, shapeFlag } = n2
 switch (type) {
  case Text:
   // 处理文本
   break
  case Comment:
   // 处理注释
   break
  // case ...
  default:
   if (shapeFlag & ShapeFlags.ELEMENT) {
    // 处理 DOM 元素
   } else if (shapeFlag & ShapeFlags.COMPONENT) {
    // 处理自定义组件
   } else if (shapeFlag & ShapeFlags.TELEPORT) {
    // 处理 Teleport 组件
    // 调用 Teleport.process 方法
    type.process(n1, n2, container...);
   } // else if ...
 }
}

可以看到,在处理 Teleport 时,最后会调用 Teleport.process 方法,Vue3 中很多地方都是通过 process 的方式来处理 vnode 相关逻辑的,下面我们重点看看 Teleport.process 方法做了些什么。

// packages/runtime-core/src/components/Teleport.ts
const isTeleportDisabled = props => props.disabled
export const Teleport = {
 __isTeleport: true,
 process(n1, n2, container) {
  const disabled = isTeleportDisabled(n2.props)
  const { shapeFlag, children } = n2
  if (n1 == null) {
   const target = (n2.target = querySelector(n2.prop.to))   
   const mount = (container) => {
    // compiler and vnode children normalization.
    if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
     mountChildren(children, container)
    }
   }
   if (disabled) {
    // 开关关闭,挂载到原来的位置
    mount(container)
   } else if (target) {
    // 将子节点,挂载到属性 `to` 对应的节点上
    mount(target)
   }
  }
  else {
   // n1不存在,更新节点即可
  }
 }
}

其实原理很简单,就是将 Teleportchildren 挂载到属性 to 对应的 DOM 元素中。为了方便理解,这里只是展示了源码的九牛一毛,省略了很多其他的操作。

总结

希望在阅读文章的过程中,大家能够掌握 Teleport 组件的用法,并使用到业务场景中。尽管原理十分简单,但是我们有了 Teleport 组件,就能轻松解决弹窗元素定位不准确的问题。

到此这篇关于详解Vue3 Teleport 的实践及原理的文章就介绍到这了,更多相关Vue3 Teleport组件内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Vue.js 相关文章推荐
详解Vue3 Teleport 的实践及原理
Dec 02 Vue.js
vue3.0中setup使用(两种用法)
Dec 02 Vue.js
Vue router安装及使用方法解析
Dec 02 Vue.js
vue实现购物车的小练习
Dec 21 Vue.js
vue-quill-editor插入图片路径太长问题解决方法
Jan 08 Vue.js
vue组件是如何解析及渲染的?
Jan 13 Vue.js
关于Vue Router的10条高级技巧总结
May 06 Vue.js
使用这 6个Vue加载动画库来减少我们网站的跳出率
May 18 Vue.js
Vue3如何理解ref toRef和toRefs的区别
Feb 18 Vue.js
Vue+Flask实现图片传输功能
Apr 01 Vue.js
如何优化vue打包文件过大
Apr 13 Vue.js
vue项目打包后路由错误的解决方法
Apr 13 Vue.js
vue $router和$route的区别详解
Dec 02 #Vue.js
基于vue项目设置resolves.alias: '@'路径并适配webstorm
Dec 02 #Vue.js
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
Dec 02 #Vue.js
vue组件中节流函数的失效的原因和解决方法
Dec 02 #Vue.js
Vue3+elementui plus创建项目的方法
Dec 01 #Vue.js
Vue.js桌面端自定义滚动条组件之美化滚动条VScroll
Dec 01 #Vue.js
vue开发chrome插件,实现获取界面数据和保存到数据库功能
Dec 01 #Vue.js
You might like
php学习笔记 面向对象中[接口]与[多态性]的应用
2011/06/16 PHP
PHP输出数组中重名的元素的几种处理方法
2012/09/05 PHP
php面向对象中的魔术方法中文说明
2014/03/04 PHP
mantis安装、配置和使用中的问题小结
2014/07/14 PHP
PHP自定义函数实现格式化秒的方法
2016/09/14 PHP
php高清晰度无损图片压缩功能的实现代码
2018/12/09 PHP
jquery cookie插件代码类
2009/05/26 Javascript
JQUERY的属性选择符和自定义选择符使用方法(二)
2011/04/07 Javascript
jquery实现显示已选用户
2014/07/21 Javascript
JavaScript保存并运算页面中数字类型变量的写法
2015/07/06 Javascript
jQuery+Ajax+PHP+Mysql实现分页显示数据实例讲解
2015/09/27 Javascript
真正好用的js验证上传文件大小的简单方法
2016/10/27 Javascript
javascript基本数据类型及类型检测常用方法小结
2016/12/14 Javascript
微信小程序学习之数据处理详解
2017/07/05 Javascript
在Vue中用canvas实现二维码和图片合成海报的方法
2019/06/10 Javascript
Vue实现日历小插件
2019/06/26 Javascript
vue-cli history模式实现tomcat部署报404的解决方式
2019/09/06 Javascript
layui在form表单页面通过Validform加入简单验证的方法
2019/09/06 Javascript
[43:51]2018DOTA2亚洲邀请赛3月30日 小组赛B组 EG VS Secret
2018/03/31 DOTA
qpython3 读取安卓lastpass Cookies
2016/06/19 Python
Python实现动态图解析、合成与倒放
2018/01/18 Python
浅谈python str.format与制表符\t关于中文对齐的细节问题
2019/01/14 Python
Pandas删除数据的几种情况(小结)
2019/06/21 Python
python中time、datetime模块的使用
2020/12/14 Python
CSS3中的Transition过度与Animation动画属性使用要点
2016/05/20 HTML / CSS
全球虚拟主机商:HostGator
2017/02/06 全球购物
北美主要的汽车零部件零售商:AutoShack.com
2019/02/23 全球购物
中国一家综合的外贸B2C电子商务网站:DealeXtreme(DX)
2020/03/10 全球购物
大学应届毕业生个人求职信
2013/09/23 职场文书
化学相关工作求职信
2013/10/02 职场文书
教师求职自荐信
2014/03/09 职场文书
数控专业毕业生求职信
2014/06/12 职场文书
计算机应用应届生求职信
2014/07/12 职场文书
授权委托书样本及填写说明
2014/09/19 职场文书
python文本处理的方案(结巴分词并去除符号)
2021/05/26 Python
python必学知识之文件操作(建议收藏)
2021/05/30 Python