Teleport 传送

  • 定义和使用

    Vue鼓励我们通过将UI和相关行为封装到组件中来构建UI。我们可以将它们彼此嵌套以构建一棵构成应用程序UI的树。
    但是,有时组件模板的一部分在逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移到DOM中Vue应用程序外部的DOM中的其他位置。
    常见的情况是创建一个包含全屏模式的组件。在大多数情况下,您希望模态的逻辑存在于组件中,但是模态的定位很快很难通过CSS来解决,或者需要更改组件的组成。
    考虑以下HTML结构。
    <body>
      <div style="position: relative;">
        <h3>Tooltips with Vue 3 Teleport</h3>
        <div>
          <modal-button></modal-button>
        </div>
      </div>
    </body>
    
    让我们来看看 modal-button。
    该组件将具有一个 button 触发模态打开的 div 元素,以及一个带有的类的元素,该类 .modal 将包含模态的内容和一个用于自动关闭的按钮。
    const app = Vue.createApp({});
      app.component('modal-button', {
        template: `
          <button @click="modalOpen = true">
              Open full screen modal!
          </button>
      
          <div v-if="modalOpen" class="modal">
            <div>
              I'm a modal! 
              <button @click="modalOpen = false">
                Close
              </button>
            </div>
          </div>
        `,
        data() {
          return { 
            modalOpen: false
          }
        }
      })
    
    当使用初始 HTML 结构内该组件,我们可以看到一个问题-模态正在呈现内的深度嵌套 div 和 position: absolute 模态的花费相对定位父 div 作为参考。
    Teleport 提供了一种干净的方法,使我们可以控制要在 DOM 中哪个父对象下呈现 HTML,而不必求助于全局状态或将其拆分为两个部分。
    让我们修改 modal-button 使用 <teleport> 并告诉 Vue 将这个 HTML 传送到 “body” 标签。
    app.component('modal-button', {
      template: `
        <button @click="modalOpen = true">
            Open full screen modal! (With teleport!)
        </button>
    
        <teleport to="body">
          <div v-if="modalOpen" class="modal">
            <div>
              I'm a teleported modal! 
              (My parent is "body")
              <button @click="modalOpen = false">
                Close
              </button>
            </div>
          </div>
        </teleport>
      `,
      data() {
        return { 
          modalOpen: false
        }
      }
    })
    
    尝试一下
  • 与Vue组件一起使用

    如果 <teleport> 包含 Vue 组件,它将保留 <teleport> 为父元素的逻辑子组件:
    const app = Vue.createApp({
      template: `
        <h1>Root instance</h1>
        <parent-component />
      `
    })
    
    app.component('parent-component', {
      template: `
        <h2>This is a parent component</h2>
        <teleport to="#endofbody">
          <child-component name="John" />
        </teleport>
      `
    })
    
    app.component('child-component', {
      props: ['name'],
      template: `
        <div>Hello, {{ name }}</div>
      `
    })
    
    在这种情况下,即使 child-component 在不同的位置进行渲染,它仍将是它的子项 parent-component 并 name 从中接收道具。
    这也意味着来自父组件的注入按预期方式工作,并且子组件将嵌套在 Vue Devtools 中的父组件下方,而不是放置在实际内容移动到的位置。
  • 在同一目标上使用多个传送

    常见的用例场景是可重用的 <Modal> 组件,其中可能同时有多个实例处于活动状态。对于这种情况,多个 <teleport> 组件可以将其内容安装到同一目标元素。顺序将是一个简单的追加-以后的安装将在目标元素内的前面的安装之后。
    <teleport to="#modals">
      <div>A</div>
    </teleport>
    <teleport to="#modals">
      <div>B</div>
    </teleport>
    
    <!-- result-->
    <div id="modals">
      <div>A</div>
      <div>B</div>
    </div>