Much has been written about Vue3’s composite API and proxy-based responsiveness principle. In addition to these notable updates, Vue3 also has a new built-in component: Teleport. The main purpose of this component is to move DOM elements within the template to other locations.

Usage scenarios

In the process of business development, we often encapsulate some common components, such as Modal components. I believe that when you use Modal components, you often encounter a problem, that is the positioning problem of Modal.

So without further ado, let’s just write a simple Modal component.

<! -- 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;
  }
  /* omit part of style */
}
</style>
<template>
  <div class="modal__mask">
    <div class="modal__main">
      <div class="modal__header">
        <h3 class="modal__title">Popup window title</h3>
        <span class="modal__close">x</span>
      </div>
      <div class="modal__content">Popover text content</div>
      <div class="modal__footer">
        <button>cancel</button>
        <button>confirm</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  setup() {
    return{}; }};</script>
Copy the code

Then we introduce the Modal component in the page.

<! -- 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>
Copy the code

As shown in the figure above, the popover component under div.container displays normally. Elements laid out with Fixed will normally be positioned relative to the screen window, but fixed will be positioned relative to the parent if the parent’s Transform, perspective, or filter attribute is not None.

We just need to change the transform of the.container class, and the orientation of the popover component will be distorted.

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

At this point, using the Teleport component solves the problem.

Teleport provides a clean way to control which parent node in the DOM renders HTML without having to resort to global state or split it into two components. — Vue official documentation

All we need to do is put the popover contents into Teleport and set the to property to body, which means that the popover component will be a child of the body every time it renders.

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

Can be in codesandbox. IO/embed/vue – m… Look at the code.

The source code parsing

We can start by writing a simple template and then look at the code generated when the Teleport component is compiled from the template.

Vue.createApp({
  template: ` 
       
       
teleport to body
`
}) Copy the code

Simplified code:

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 */)))}}Copy the code

You can see that the Teleport component is created with createBlock.

// packages/runtime-core/src/renderer.ts
export function createBlock(
	type, props, children, patchFlag
) {
  const vnode = createVNode(
    type,
    props,
    children,
    patchFlag
  )
  / /... Omitting logic
  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(){}}Copy the code

The first argument to createBlock is Teleport, and the resulting vNode has a shapeFlag property that indicates the type of vNode. IsTeleport (type) yields true, so the final value of the shapeFlag property is shapeflags.teleport (1 << 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
}
Copy the code

The Render node of the component takes different logic depending on type and shapeFlag.

// packages/runtime-core/src/renderer.ts
const render = (vnode, container) = > {
  if (vnode == null) {
    // If the current component is empty, the component is destroyed
    if (container._vnode) {
      unmount(container._vnode, null.null.true)}}else {
    // Create or update a component
    // Container._vnode is the cache of previously created components
    patch(container._vnode || null, vnode, container)
  }
  container._vnode = vnode
}

// Patch is used to create, update, and destroy vNodes
const patch = (n1, n2, container) = > {
  // If the types of the old and new nodes are different, the old node is destroyed
  if(n1 && ! isSameVNodeType(n1, n2)) { unmount(n1) }const { type, ref, shapeFlag } = n2
  switch (type) {
    case Text:
      // Process text
      break
    case Comment:
      // Handle comments
      break
    // case ...
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        // Handle DOM elements
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        // Handle custom components
      } else if (shapeFlag & ShapeFlags.TELEPORT) {
        // Process the Teleport component
        // Call the teleport.process methodtype.process(n1, n2, container...) ; }// else if ...}}Copy the code

As you can see, teleport. process is called at the end of the Teleport process. Much of Vue3 uses process to handle vNode logic. Let’s focus on what the teleport.process method does.

// 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) {
        // Switch off, mount to original position
        mount(container)
      } else if (target) {
        // Mount the child node to the node corresponding to the attribute 'to'
        mount(target)
      }
    }
    else {
      // N1 does not exist}}}Copy the code

The principle is simple: Teleport’s children is mounted to the DOM element corresponding to the attribute to. For ease of understanding, this is just a sliver of the source code, omitting many other operations.

conclusion

Hopefully, you will be able to grasp the usage of the Teleport component and use it in business scenarios. Although the principle is very simple, with the Teleport component, we can easily solve the problem of inaccurate orientation of popover elements.