Practice and principle of vue3 teleport component

Time:2021-2-15

Vue3’s composite API and the principle based on proxy response have been introduced in many articles. In addition to these bright updates, vue3 also adds a built-in component: teleport. This component is mainly used to move DOM elements in 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 in the process of using modal components, we often encounter a problem, that is, the positioning problem of modal.

Let’s write a simple modal component first.

<!-- 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 style*/
}
</style>
<template>
  <div class="modal__mask">
    <div class="modal__main">
      <div class="modal__header">
        <h3 class="modal__ Title > pop up title</h3>
        <span class="modal__close">x</span>
      </div>
      <div class="modal__content">
        Pop up text content
      </div>
      <div class="modal__footer">
        < button > cancel
        < button > confirm
      </div>
    </div>
  </div>
</template>

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

Then we introduce the modal component into 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>

Practice and principle of vue3 teleport component

As shown in the figure above,div.containerThe pop-up window assembly is displayed normally. usefixedIn general, the layout elements will be positioned relative to the screen window, but if the parent element’stransform, perspectiveorfilterProperty is notnoneWhen,fixedThe element is positioned relative to the parent element.

We just need to.containerClasstransformWith a little modification, the positioning of pop-up components will be confused.

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

Practice and principle of vue3 teleport component

At this time, useTeleportComponents can solve this problem.

Teleport provides a clean way to control which parent node in the DOM renders HTML without resorting to global state or splitting it into two components. –Vue official documents

We just need to put the pop-up content in theTeleportAnd set thetoProperty isbody, which means that the pop-up component will be rendered asbodySo that the previous problem can be solved.

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

Can be in https://codesandbox.io/embed/vue-modal-h5g8y Look at the code.

Practice and principle of vue3 teleport component

Source code analysis

We can write a simple template first, and then look at itTeleportComponent after template compilation, generated code.

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

Practice and principle of vue3 teleport component

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 */)
    ]))
  }
}

You can see thatTeleportComponent passedcreateBlockCreate.

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

afferentcreateBlockThe first parameter of isTeleportIn the final vnode, there will be oneshapeFlagProperty that represents the type of vnode.isTeleport(type)The results are as followstrueSoshapeFlagThe last value of the property isShapeFlags.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
}

In the render node of the component, thetypeandshapeFlagDifferent logic.

// packages/runtime-core/src/renderer.ts
const render = (vnode, container) => {
  if (vnode == null) {
    //If the current component is empty, the component will be destroyed
    if (container._vnode) {
      unmount(container._vnode, null, null, true)
    }
  } else {
    //Create or update components
    // container._ Vnode is the cache for components that have been previously created
    patch(container._vnode || null, vnode, container)
  }
  container._vnode = vnode
}

//Patch is a patch used to create, update and destroy vnode
const patch = (n1, n2, container) => {
  //If the old and new nodes are of different types, the old node will be destroyed
  if (n1 && !isSameVNodeType(n1, n2)) {
    unmount(n1)
  }
  const { type, ref, shapeFlag } = n2
  switch (type) {
    case Text:
      //Processing text
      break
    case Comment:
      //Processing notes
      break
    // case ...
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        //Processing DOM elements
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        //Processing custom components
      } else if (shapeFlag & ShapeFlags.TELEPORT) {
        //Handling teleport components
        //Call Teleport.process  method
        type.process(n1, n2, container...);
      } // else if ...
  }
}

As you can see, in the process of processingTeleportFinally, theTeleport.processMethod. In vue3, many places deal with vnode related logic through process. Let’s focus on itTeleport.processWhat does the method do.

// 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, update the node
    }
  }
}

In fact, the principle is very simpleTeleportOfchildrenMount to propertytoIn the corresponding DOM element. In order to facilitate understanding, this is just a drop in the bucket of the source code, omitting many other operations.

summary

I hope that in the process of reading the article, we can master itTeleportComponent usage, and used in business scenarios. Although the principle is very simple, we have itTeleportComponent can easily solve the problem of inaccurate positioning of pop-up elements.

Practice and principle of vue3 teleport component