「Vue系列」欢迎传送到“Teleport”星球

news2025/7/21 21:36:42

前言

大家好,我是落叶小小少年,我一直谨记学习不断,分享不停,输入的最好方式是输出,我始终相信

  • 用最核心代码更容易理解深的技术点
  • 用通俗易懂的话,讲难的知识点

之前有学习并写了KeepAlive组件的实现原理,后来打算也把Teleport组件的原理也学习并记录下来,于是这几天便学习了下Teleport组件的实现原理,现在分享给大家,希望能和大家共同学习,进步

Tips: 这样面试的时候你就可以信心满满的向面试官讲解这个知识点了🫣

Teleport是什么

Teleport 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去

Teleport组件的功能

1. 不用有什么问题

想象一下如果你需要一个模态框的功能,这个组件的模板app组件内,但从整个应用试图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方
假设我们有一个模态框,并且是下面这样的写法

<div class="outer">
  <MyModal />
</div>

在这里插入图片描述

这个MyModel组件是一个模态框,并且会被渲染到class为outer的的div标签下,但是我们通常希望这个模态框的蒙层能过遮挡页面上的任何元素

那么我们把这个组件的z-index设置的最高,但是问题是模态框的z-index会受限于它的容器元素,如果有其他元素与 <div class="outer"> 重叠并有更高的 z-index,则它会覆盖住我们的模态框,所以我们自己实现这种效果就不太理想

于是就有了Teleport组件的,它的功能就行为了解决这类受限制的dom问题,它可以将组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去

2. 如何使用

<Teleport to="body">
  <div class="modal">
    <p>Hello from the modal!</p>
  </div>
</Teleport>

在这里插入图片描述

Teleport的to属性就是指定挂载的位置,上面我们会将<div class=“modal”> 渲染到body上,而不会按照模板的dom层级渲染,于是就实现了dom的跨层级渲染

tips*: 如果to的目标元素是由Vue渲染的,那么必须确保在挂载 <Teleport> 之前先挂载该元素

如何实现

Teleport组件在渲染的时候走组件内部的渲染,而不走通用的渲染逻辑,这需要渲染器的支持,也就是在mountunmountmove的时候做特殊渲染处理

不了解挂载过程的可以去看Vue内置组件之KeepAlive原理里的组件的挂载过程

简单实现(实现一个小而易懂的Teleport组件)

1. Teleport 组件的属性

type TeleportProps = {
  to: string | RendererElement | null // string或者已渲染的目标元素
  disabled?: boolean
}
export const TeleportImpl = {
  // 用来标识是否是Teleport组件
  __isTeleport: true,
  process(
    n1: TeleportVNode | null,
    n2: TeleportVNode,
    container: RendererElement,
    anchor: RendererNode | null,
    internals: RendererInternals
  ) {
	// 这里是渲染逻辑
  },
  remove(
    vnode: TeleportVNode, { o: { remove: hostRemove }, um: unmount   }) {
	// 这里是销毁逻辑
  }
  move(n2: TeleportVNode, container: RendererElement, anchor: RendererNode, internals: RendererInternals) {
	// 这里是move逻辑,move被Teleport渲染的内容
  }

2.修改渲染器的渲染逻辑

2.1 修改patch的渲染逻辑

在patch函数里面判断是否是Teleport组件,如果是的话那么将渲染控制权交给Teleport组件,调用process函数去挂载children

patch函数其中就包括mount和patch功能

const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null
  ) {
	if (n1 === n2) {
      return
    }
    if (n1 && !isSameVNodeType(n1, n2)) {
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }
    const { type, shapeFlag } = n2
	switch (type) {
	  case Text:
		//...
	  case Comment:
		//...
	  case Fragment:
		//...
	  default:
		// 通过shapeFlag进行判断,这个在解析Teleport组件时就设置
		if (shapeFlag & ShapeFlags.TELEPORT) {
			// 渲染时直接调用其process函数去渲染
		  ;(type as typeof TeleportImpl).process(
              n1 as TeleportVNode,
              n2 as TeleportVNode,
              container,
              anchor,
              internals
            )
	    }
	}
  }

2.2 修改move的渲染逻辑

修改move的渲染逻辑
在Teleport组件需要move的时候不需要走渲染器的move函数,而是将其拦截并进行一些处理比如挂载Text视图

const move: MoveFn = (
	vnode,
    container,
    anchor,
	internals
  ) {
	const { el, type, transition, children, shapeFlag } = vnode
	if (shapeFlag & ShapeFlags.TELEPORT) {
      ;(type as typeof TeleportImpl).move(vnode, container, anchor, internals)
      return
    }
}

2.3 修改unmount的销毁逻辑

修改unmount的渲染逻辑
当Teleport组件销毁时,Teleport的字组件是有不同的销毁逻辑的,所以在判断时Teleport组件时会调用对应的remove函数进行卸载

const unmount: UnmountFn = (
    vnode,
    parentComponent,
    parentSuspense,
    doRemove = false,
    optimized = false
) => {
	const {
      children,
      shapeFlag,
    } = vnode
	// ...
	if (shapeFlag & ShapeFlags.TELEPORT) {
        ;(vnode.type as typeof TeleportImpl).remove(
          vnode,
          parentComponent,
          parentSuspense,
          optimized,
          internals,
          doRemove
        )
    }
}

3.编译Teleport组件

编译器在编译Teleport组件时,在编译Teleport组件的vnode时会设置shapeFlag的值设置ShapeFlags.TELEPORT

在这里插入图片描述

同时在解析children的时候也会将其字节点编译成一个数组,而不像其他组件会被编译为插槽内容,所以在渲染字组件的时候只需要遍历数组就行

export function normalizeChildren(vnode: VNode, children: unknown) {
	const { shapeFlag } = vnode
    // ....
	if (children == null) {
       children = null
    } else if (isArray(children)) {
    	type = ShapeFlags.ARRAY_CHILDREN
	} else {
		// 保证Teleport的children一定为ARRAY_CHILDREN类型
		if (shapeFlag & ShapeFlags.TELEPORT) {
      	    type = ShapeFlags.ARRAY_CHILDREN
      	    children = [createTextVNode(children as string)]
    	}
	}
}

4. 挂载Teleport组件

我们来简易实现Teleport组件的process挂载函数

process函数的实在渲染器的 patch函数里面调用的,那么我们知道patch函数主要的功能就是mount和patch功能,在Teleport组件里也是如此,要对n1和n2进行判处和处理

在container主视图中插入锚点信息没有特意写出来

  process(
    n1: TeleportVNode | null,
    n2: TeleportVNode,
    container: RendererElement,
    anchor: RendererNode | null,
    internals: RendererInternals
  ) {
	// 拿到渲染器的一些方法
    const {
      mc: mountChildren,
      pc: patchChildren,
      o: { insert, querySelector, createComment }
    } = internals
	// 判断要是否是disabled
    const disabled = isTeleportDisabled(n2.props)
	// 解构shapeFlag, children
    const { shapeFlag, children } = n2;
	// 挂载的场景
    if (n1 == null) {
	  // 在container主视图中插入锚点信息
      const placeholder = (n2.el = __DEV__
        ? createComment('teleport start')
        : createText(''))
	  insert(placeholder, container, anchor)
  	  // ...teleport end锚点
	  // 通过props上的to获取到要挂载的target元素
      const target = (n2.target = resolveTarget(n2.props, querySelector));
	  // 被禁用
      if (disabled) {
		// 直接原地挂载,挂载到container上
        if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
		  // 上面提到在编译阶段对于Teleport会将children序列化为array类型
		  // 调用渲染器的mountChildren函数挂载到container上
          mountChildren(children as VNodeArrayChildren, container, anchor, internals);
        }
	   // 未被禁用
      } else {
        if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
		  // 调用渲染器的mountChildren函数挂载到target上
          mountChildren(children as VNodeArrayChildren, target, null, internals)
        }
      }
    // patch的场景
    } else {
	  // 简化,Vue还会有很多判断,比如从disabled到enabled
	  //enabled到disabled以及target相同的情况
      n2.el = n1.el
      const target = n2.target = n1.target
	  // 获取到新的target元素
      const nextTarget = resolveTarget(n2.props, querySelector)
	  // 先去patchChildren,更新children
      patchChildren(n1, n2, target, anchor, internals, false)
	  // 将patch后的n2 vnode直接move到新的target上即可
     // 下文会实现,就是move时调用的函数
      moveTeleport(n2, nextTarget, anchor, internals, TeleportMoveTypes.TARGET_CHANGE);
    }
  }

isTeleportDisabled函数主要是获取props上的disabled属性,返回是否是disabled

function isTeleportDisabled(props: VNode['props']): boolean {
  return props && (props.disabled || props.disabled === '')
}

resolveTarget函数主要是获取props上的to属性,通过对to的判断,返回对应获取的dom或者是用户传递的dom

如果是string类型则会通过document.querySelector去返回对应的dom元素,否则直接返回

所以在传递to的时候要考虑挂载id的话传递#xxx

// props类型
type TeleportProps = {
  to: string | RendererElement | null
  disabled?: boolean
}
function resolveTarget(
  props: TeleportProps | null,
  select: RendererInternals['o']['querySelector']
) {
  const targetSelector = props && props.to
  // 简写,vue还会做警告信息处理和 null返回等
  // select函数就是querySelector
  if (typeof targetSelector === 'string' && select) {
    return select(targetSelector)
  } else {
    return targetSelector as RendererElement
  }
}

5. 移动Teleport组件

当patch到Teleport组件时,也会走到渲染器里的move逻辑,那么Teleport组件的move逻辑是怎么实现的呢?

// Teleport组件move的类型
export const enum TeleportMoveTypes {
  TARGET_CHANGE,  // target change
  TOGGLE, // enable / disable
  REORDER // moved in the main view
}
function moveTeleport(vnode: VNode, container: RendererElement, parentAnchor: RendererNode, internals: RendererInternals, moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER) {
  const { o: { insert }, m: move } = internals;
  // 如果是target change,则直接移动target锚点
  if (moveType === TeleportMoveTypes.TARGET_CHANGE) {
    insert(vnode.targetAnchor!, container, parentAnchor)
  }
  const { el, props, shapeFlag, anchor: _anchor, children } = vnode;
  const isReorder = moveType === TeleportMoveTypes.REORDER
  // 如果这是重新排序,则移动主视图锚点
  if (isReorder) {
    insert(el!, container, parentAnchor)
  }
   // 如果这是重新排序并且传送已启用(内容在目标中)不要移动孩子。
  // 所以相反的是:只有在这种情况下才移动孩子
  // 不是重新排序,或者传送被禁用
  if (!isReorder || isTeleportDisabled(props)) {
    if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
      for (let i = 0; i < children.length; i++) {
        move(children[i], container, parentAnchor, MoveType.REORDER)
      }
    }
  }
}

6. 销毁Teleport组件

remove: (vnode: VNode, parentComponent: ComponentInternalInstance | null,
    optimized: boolean,
    { um: unmount, o: { remove: hostRemove } }: RendererInternals,
    doRemove: Boolean
  ) => {
    const { shapeFlag, children, anchor, props } = vnode;
	// 主动remove或者非disabled的情况下要将挂载的字节点销毁
    if (doRemove || !isTeleportDisabled(props)) {
      hostRemove(anchor!)
      if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
        for (let i = 0; i < children.length; i++) {
		  // 调用渲染器上的unmount函数
          unmount(children[i], parentComponent, null, true, true)
        }
      }
    }
  }

这里只写了移除子节点,其实还会移除主视图渲染的锚点(teleport start或注释节点以及teleport end或注释节点)

画了一个简单流程图方便大家一眼看明白整体流程

在这里插入图片描述

写在最后

以上我们抽离Vue中的Teleport组件的主要代码进行分析讲解,当你知道Teleport组件的基本原理后使用上更加清晰明了

最后总结一下Teleport组件的实现原理:

  1. 首先在编译Teleport组件的时候会在vnode的shapeFlag上做标记,将其设置为ShapeFlags.TELEPORT,然后在normalizeChildren的时候判断shapeFlag值,特殊处理children,将children设置为array
  2. Teleport组件在挂载的时候调用patch渲染器的patch方法,然后在调用组件内的process函数,process函数则有组件创建和组件更新的逻辑
  3. 创建的时候会通过to属性拿到target元素,然后判断是否为disabled,如果是则不渲染到target上,否则就渲染到target上
  4. Teleport 组件创建首先会在在主视图里插入注释节点或者空白文本节点,最后调用mountChildren方法创建子节点往target目标元素插入 Teleport 组件的子节点
  5. Teleport 组件更新首先会更新子节点,处理 disabled 属性变化的情况,处理 to 属性变化的情况,决定要不要move子节点
  6. Teleport组件move的时候不是重新排序,或者传送被禁用的时候才move子节点
  7. 销毁的时候Teleport组件会调用内部的remove函数,移除主视图渲染的锚点,同时判断在主动remove或者非disabled的情况下要将挂载的字节点销毁

PS: 如果要想了解KeepAlive组件的实现原理,也可以看Vue内置组件之KeepAlive原理

如果对你有帮助的话,不妨点赞收藏关注一下,谢谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/35689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx 报错问题汇总(持续更新ing)

目录 一、nginx: [emerg] invalid number of arguments in "include" directive in C:\Program Files\nginx-1.15.4/conf/nginx.conf:61 总结&#xff1a; 二、nginx: [error] OpenEvent("Global\ngx_reload_2152") failed (5: Access is denied) 解决方…

《Java》图书管理系统(已升级)

目录 前言 效果展示 功能模块 书架 定义书的类 创建书架 用户 User用户类 AdminUser管理员 NormalUser普通用户 功能 接口 FindOperation 查找图书 AddOperation添加图书 DelOperation删除图书 BorrowOperation借阅图书 DisOperation 打印图书 RetOperation归还图…

Spring Boot 3.0.0 GA版本正式发布,期待已久的SpringBoot3发布了

期待已久的SpringBoot3.0.0发布了发布说明新版本的亮点分析1. Java 17 baseline 和 Java 19 支持2. 支持 [GraalVM native images](https://docs.spring.io/spring-boot/docs/3.0.0/reference/html/native-image.html#native-image)&#xff0c;取代实验性的 Spring Native 项目…

linux篇【11】:linux下的线程<中序>

目录 一.线程互斥 1.三个概念 2.互斥 &#xff08;1&#xff09;在执行语句的任何地方&#xff0c;线程可能被切换走 &#xff08;3&#xff09;抢票场景中的问题 &#xff08;4&#xff09;解决方案 3.加锁 &#xff08;1&#xff09;加锁介绍 &#xff08;2&#xf…

STC 51单片机39——汇编语言 按钮流水灯

每按一下按钮&#xff0c;灯就移动一个 ORG 0000H LJMP MAIN ORG 0003H ;中断矢量 LJMP INT MAIN:SETB EA ;开总中断允许“开关” SETB EX0 ;开分中断允许“开关” SETB PX0 ;高优先级 SETB IT0 ;边沿触发 MOV A,#0FEH ;给…

Linux系统编程(二)——Linux系统IO函数

在第一篇的时候写到了系统环境的搭建以及各种调试的方法&#xff0c;接下来讲述关于系统函数的使用。 目录 0x01 标准C库IO函数和Linux系统IO函数对比 一、标准C库IO函数操作流程 二、标准C库IO和Linux系统IO的关系 三、虚拟地址空间 0x02 LinuxIO函数实例 一、open()、…

dubbo以xml方式操作和新版dubbo-admin安装

文章目录1 dubbo xml配置1.1 提供者1.1.1 提供者接口1.1.2 提供者实现类1.1.2.1 项目结构图示1.1.2.2 pom.xml1.1.2.3 实现类接口1.1.2.4 配置文件1.1.2.4.1 xml配置1.1.2.4.2 结合注解方式1.1.2.5 启动类1.1.2.5.1 直接读取配置文件1.1.2.5.2 Main.main启动1.1.3 其他方式配置…

最新持续更新Crack:LightningChart 行业使用大全

LightningChart .NET 和 JavaScript 解决方案旨在通过彻底的图表优化、最小的延迟和流畅的呈现来满足行业最苛刻的数据可视化要求。LightningChart .NET 和 JavaScript 直观的 API 使用户能够使用适用于任何桌面、移动和平板设备的鼠标和触摸屏交互功能轻松操作最复杂的图表。 …

JSP JAVA javaweb企业仓库库存管理系统(仓库进销存管理系统ssm库存管理系统仓库管理系统)

JSP 企业仓库库存管理系统&#xff08;仓库进销存管理系统ssm库存管理系统仓库管理系统&#xff09;

【附源码】计算机毕业设计JAVA疫情下的进出口食品安全信息管理系统

【附源码】计算机毕业设计JAVA疫情下的进出口食品安全信息管理系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&…

Django Cookie 与 Session 对比

文章目录原理比较语法比较Cookie 示例创建 Cookie更新 Cookie删除 CookieSession 示例创建 session查询 session删除一组键值对删除 session参考文档本文通过示例演示 Django 中如何创建、查询、删除 Cookie 与 Session。 原理比较 在Web开发中&#xff0c;使用 session 来完成…

无线监控摄像头使用什么样的物联网卡?

无线监控摄像头使用什么样的物联网卡&#xff1f; 随着安全行业的发展和进步&#xff0c;无线监控摄像头的种类越来越多&#xff0c;但主要的网络形式大致可以分为两种形式&#xff0c;一种是连接WiFi使用远程监控&#xff0c;另一种是插入物联网卡的远程监控。随着物联网的快…

初阶数据结构学习记录——열 二叉树(3)链式

链式二叉树是由指针形成的二叉树&#xff0c;之前写的二叉树是由数组组成的&#xff0c;链式由链表来做。链式二叉树每个节点有两个指针&#xff0c;指向两边。以往二叉树&#xff0c;栈&#xff0c;队列等等都需要增删查改&#xff0c;但链式二叉树则不是这样&#xff0c;是因…

深入理解java虚拟机:虚拟机字节码执行引擎(1)

文章目录1. 概述2. 运行时栈帧结构2.1 局部变量表2.2 操作数栈2.3 动态连接2.4 方法返回地址2.5 附加信息1. 概述 代码编译的结果是从 本地机器码 转变为 字节码 &#xff0c;是存储格式发展的一小步&#xff0c;却是编程语言发展的一大步。 执行引擎 是Java虚拟机最核心的组…

pmp是什么意思啊?

PMP是一个证书&#xff0c;项目管理类的专业认证考试&#xff0c;从国外引进大陆已经很多年了&#xff0c;反响也不错。 以前&#xff0c;大陆每年报考PMP的人很少&#xff0c;那时的思维观念&#xff0c;更多的认为有了这个PMP证书&#xff0c;代表着你很上进&#xff0c;学习…

Terraform 初始化慢~配置本地离线源

解决Terraform初始化慢~配置本地离线源 - 知乎 这里不再介绍Terraform是啥了&#xff0c;可以参考最近上线的课程。直奔主题&#xff0c;配置一个离线的源。需要手动或者terraform init一次下载&#xff0c; 然后缓存。后续直接使用缓存。 本次实践使用的是Linux/Mac 系统&am…

【App自动化测试】(十二)App异常弹框处理

目录1. app弹框异常处理——递归方式1.1 黑名单弹框异常处理逻辑1.2 实现代码1.3 方法缺点2. app弹框异常处理——装饰器版本2.1 装饰器的优势2.2 实现代码前言&#xff1a; 本文为在霍格沃兹测试开发学社中学习到的一些技术写出来分享给大家&#xff0c;希望有志同道合的小伙伴…

计算机毕业设计之java+ssm交通信息网上查询系统

项目介绍 随着交通交通管理需求和在线交通管理渗透率的提升&#xff0c;中国交通管理在线市场将释放巨大潜力&#xff0c;交通管理系统的建设和发展成为业界广泛关注的重点&#xff0c;本文将对此进行分析&#xff0c;以期为我国交通管理电子商务的发展提供参考。交通管理业对…

石化能源行业工业互联网智能工厂解决方案

随着时代的发展&#xff0c;中国的工业企业逐渐进入了一个“新常态”&#xff1a;生产效率提升&#xff0c;非计划停运或检修造成的生产损失更为昂贵&#xff1b;高盈利的要求&#xff0c;需要更加关注能源使用效率&#xff1b;法律法规对于人员安全及环保合规要求更为严格&…

基于ffmpeg开发的多音频文件音量均衡程序

前言 audio_balance ✨ 基于ffmpeg开发的多音频文件音量均衡程序 ✨ 项目地址 GitHub&#xff1a;https://github.com/Ikaros-521/audio_balance gitee&#xff1a;https://gitee.com/ikaros-521/audio_balance 使用说明 Python&#xff1a;3.9 程序依赖 ffmpeg实现。请先安…