当前位置: 首页 > Web前端 > vue.js

Vue3Teleport组件的实践和原理

时间:2023-03-31 17:13:42 vue.js

.modal{&__mask{位置:固定;顶部:0;左:0;宽度:100vw;高度:100vh;背景:rgba(0,0,0,0.5);}&__main{保证金:0自动;底部保证金:5%;保证金顶部:20%;宽度:500px;背景:#fff;边界半径:8px;}/*省略部分样式*/>Vue3的结合API和基于Proxy的响应式原理在很多文章中都有介绍。除了这些比较引人注目的更新之外,Vue3还新增了一个内置组件:Teleport。该组件的作用主要是用来将模板中的DOM元素移动到其他位置。在使用场景业务开发的过程中,我们经常会封装一些常用的组件,比如Modal组件。相信大家在使用Modal组件的过程中,经常会遇到一个问题,那就是Modal的定位问题。话不多说,先写一个简单的Modal组件。.modal{&__mask{位置:固定;顶部:0;左:0;宽度:100vw;高度:100vh;背景:rgba(0,0,0,0.5);}&__main{保证金:0自动;底部保证金:5%;保证金顶部:20%;宽度:500px;背景:#fff;边界半径:8px;}/*省略部分样式*/>接着我们在页面组件中引入Modal。.container{height:80vh;边距:50px;overflow:hidden;}如上图所示,div.container弹出Components正常显示。使用fixed布局的元素在正常情况下会相对于screenview定位,但如果父元素的transform、perspective或filter属性不为none,则fixed元素将相对于父元素定位。我们只需要稍微修改一下.container类的transform,弹窗组件的定位就乱了。.container{高度:80vh;边距:50px;溢出:隐藏;transform:translateZ(0);}这时候使用Teleport组件就可以解决这个问题。Teleport提供了一种简洁的方式,让我们可以控制DOM中的哪个父节点渲染HTML,而无需求助于全局状态或将其拆分为两个组件。--Vue官方文档我们只需要将弹窗内容放入Teleport中,并将to属性设置为body即可,也就是说每次弹窗组件都会作为body的child使用rendered,这样前面的问题就可以解决了。可以在以下位置找到https://codesandbox.io/embed/vue-modal-h5g8y查看代码。对于源码分析,我们可以先写一个简单的模板,然后再看模板编译后Teleport组件生成的代码。Vue.createApp({template:`

teleporttobody
`})简化代码:functionrender(_ctx,_cache){with(_ctx){const{createVNode,openBlock,createBlock,Teleport}=Vuereturn(openBlock(),createBlock(Teleport,{to:"body"},[createVNode("div",null,"teleporttobody",-1/*HOISTED*/)]))}}可以看到Teleport组件是通过createBlock创建的。//packages/runtime-core/src/renderer.tsexportfunctioncreateBlock(type,props,children,patchFlag){constvnode=createVNode(type,props,children,patchFlag)//...省略部分分发returnvnode}exportfunctioncreateVNode(type,props,children,patchFlag){//类和样式规范化。if(props){//...}//将vnode类型信息编码成位图constshapeFlag=isString(type)?ShapeFlags.ELEMENT:__FEATURE_SUSPENSE__&&isSuspense(类型)?ShapeFlags.SUSPENSE:isTeleport(类型)?ShapeFlags.TELEPORT:isObject(类型)?ShapeFlags.STATEFUL_COMPONENT:isFunction(类型)?ShapeFlags.FUNCTIONAL_COMPONENT:0constvnode:VNode={type,props,shapeFlag,patchFlag,key:props&&normalizeKey(props),ref:props&&normalizeRef(props),}returnvnode}//包/runtime-core/src/成分/Teleport.tsexportconstisTeleport=type=>type.__isTeleportexportconstTeleport={__isTeleport:true,process(){}}传入createBlock的第一个参数是Teleport,最后的vnode会有一个shapeFlag属性,这是isTeleport的结果(type)用来表示vnode的类型为真,所以shapeFlag属性的最终值为ShapeFlags.TELEPORT(1<<6)。//packages/shared/src/shapeFlags.tsexportconstenumShapeFlags{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节点中,会根据type和不同使用不同的逻辑形状标志。//packages/runtime-core/src/renderer.tsconstrender=(vnode,container)=>{if(vnode==null){//如果当前组件为空,组件将被销毁if(container._vnode){unmount(container._vnode,null,null,true)}}else{//创建或更新一个组件//container._vnode是之前创建的组件的缓存补丁(container._vnode||null,vnode,container)}container._vnode=vnode}//patch是一个补丁,用于创建、更新、销毁vnodeconstpatch=(n1,n2,container)=>{//如果新旧节点的类型不一致,销毁旧节点if(n1&&!isSameVNodeType(n1,n2)){unmount(n1)}const{type,ref,shapeFlag}=n2switch(type){caseText://handletextbreakcaseComment://handlecommentbreak//case...default:if(shapeFlag&ShapeFlags.ELEMENT){//处理DOM元素}elseif(shapeFlag&ShapeFlags.COMPONENT){//处理自定义组件}elseif(shapeFlag&ShapeFlags.TELEPORT){//处理Teleport组件//调用Teleport.process方法type.process(n1,n2,contai呃...);}//elseif...}}可以看到在处理Teleport的时候,最后会调用Teleport.process方法。Vue3中很多地方都是通过process来处理vnode相关的逻辑。让我们关注一下Teleport.process方法的作用//packages/runtime-core/src/components/Teleport.tsconstisTeleportDisabled=props=>props.disabledexportconstTeleport={__isTeleport:true,process(n1,n2,container){constdisabled=isTeleportDisabled(n2.props)const{shapeFlag,children}=n2if(n1==null){consttarget=(n2.target=querySelector(n2.prop.to))constmount=(container)=>{//compilerandvnodechildrennormalization.if(shapeFlag&ShapeFlags.ARRAY_CHILDREN){mountChildren(children,container)}}if(disabled){//关闭,挂载到原来的位置mount(container)}elseif(target){//挂载子节点,在属性`to`对应的节点上挂载(target)}}else{//n1不存在,更新节点即可}}}其实原理很简单,就是将Teleport的children挂载到中间属性对应的DOM元素。为了便于理解,这里只是粗略浏览一下源码,省略了很多其他的操作。总结希望大家在阅读文章的过程中,能够掌握Teleport组件的用法,并在业务场景中使用。虽然原理很简单,但是我们可以通过Teleport组件轻松解决弹出元素??定位不准确的问题。