Vue3.0 adds a Teleport component that developers can use to move parts of their component templates to a specific DOM location, such as body or anywhere else.

Vue 2.0 needs to use portal-Vue tripartite library or DOM manipulation with $EL to achieve the corresponding functions.

Next, we will introduce the two aspects of use and implementation principle respectively.

TeleportUse of components

TeleportComponents are simple to use by wrapping up the content that needs to be moved:
<teleport :to="body" :disabled="false"> <div> Content to move </div> </teleport>Copy the code

The result of this code is that

will render the content that needs to be moved

on the body, not where the component’s template is.

Teleport takes two arguments:

  1. toFor the position to be moved, it can be a selector or a DOM node;
  2. disabledIf it istrue, the content does not move,disabledIf it isfalse,TeleportThe wrapped element node is moved totoUnder the node of
Example: Implement something in theComponent in the template.In the template of the child componentbodySwitch between.
  • Child componentsThere is a#teleport1node
<! Vue --> <template> <div id="teleport1"> <h4> </h4> </div> </template>Copy the code
  • APP componentcontainsChild componentsThere is a buttonbuttonToggle location and what needs to be delivered<div class="send_content">{{ showingString }}</div>
<template> <sub-container /> <button class=" BTN "@click="changePosition"> </button> <teleport :to="to" :disabled="disabled"> <div class="send_content">{{ showingString }}</div> </teleport> </template> <script lang="ts"> import SubContainer from "./components/SubContainer.vue"; import { defineComponent, ref } from "vue"; Enum TeleportPosition {currentInstance, // subInstance, // body, // body } export default defineComponent({ name: "App", components: {SubContainer,}, the setup () {/ / position let position = ref (TeleportPosition. CurrentInstance); // display string content let showingString = ref(" display content in APP component "); // Whether to disable teleport let disabled = ref(true); // Mount DOM node let to = ref("body"); / / switch position let changePosition = () = > {the if (position, value = = TeleportPosition currentInstance) {position. Value = TeleportPosition.subInstance; Showingstring. value = "Contents displayed in child components "; disabled.value = false; to.value = "#teleport1"; } else if (position.value == TeleportPosition.subInstance) { position.value = TeleportPosition.body; Showingstring. value = "Contents displayed in body "; disabled.value = false; to.value = "body"; } else { position.value = TeleportPosition.currentInstance; Showingstring. value = "Contents displayed in APP component "; disabled.value = true; to.value = "body"; }}; return { showingString, to, disabled, changePosition }; }}); </script>Copy the code
  • That’s what the above code does<div class="send_content">{{ showingString }}</div>This part of the DOM can be found in theAPP componentDOM node of,Child componentsDOM nodes andbodyTo select mount.

TeleportComponent implementation principles

TeleportComponent mount

We know that the component mount first enters the patch function:

<! -- render. Ts - > const patch: PatchFn = () = > {/ / omit the other... If (shapeFlag & shapeflags.teleport) {; (type as typeof TeleportImpl).process( n1 as TeleportVNode, n2 as TeleportVNode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals ) } }Copy the code

If VNode is a Teleport component when the patch function is executed, the TeleportImpl process method is executed.

Const placeholder = (n2.el = __DEV__? createComment('teleport start') : createText('')) const mainAnchor = (n2.anchor = __DEV__ ? createComment('teleport end') : createText('')) insert(placeholder, container, anchor) insert(mainAnchor, container, anchor) // 2. Const target = (n2.target = resolveTarget(n2.props, querySelector)) const targetAnchor = (n2.targetAnchor = createText('')) if (target) { insert(targetAnchor, target) isSVG = isSVG || isTargetSVG(target) } const mount = (container: RendererElement, anchor: RendererNode) => { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( children as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } } // 3. Insert child node of 'Teleport' component in target element if (disabled) {mount(container, mainAnchor)} else if (target) {mount(target, targetAnchor)}Copy the code

The specific logic is as follows:

  1. Create a nodemainAnchorThe development environment is oneComment nodeThe release environment is oneEmpty text nodeTo create thismainAnchorThe node is mounted under the DOM node corresponding to the parent component.
  2. usequerySelectorfindTeleportcomponenttoProperty to specify the nodetargetTarget node, and then intargetAnchorCreate an empty text node under the node as the anchor node;
  3. ifTeleportcomponentdisabledAttribute values fortrueThat will beTeleportThe child nodes of the component are mounted onmainAnchorH, ifdisabledAttribute values forfalseThat will beTeleportThe child nodes of the component are mounted on the target nodetargetAnchor.

TeleportComponent updates

// data n2.el = n1.el const mainAnchor = (n2.anchor = n1.anchor)! const target = (n2.target = n1.target)! const targetAnchor = (n2.targetAnchor = n1.targetAnchor)! const wasDisabled = isTeleportDisabled(n1.props) const currentContainer = wasDisabled ? container : target const currentAnchor = wasDisabled ? mainAnchor : targetAnchor isSVG = isSVG || isTargetSVG(target) // 1. If (dynamicChildren) {// fast path when the teleport happens to be a block root patchBlockChildren( n1.dynamicChildren! , dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG, slotScopeIds ) traverseStaticChildren(n1, n2, true) } else if (! optimized) { patchChildren( n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG, SlotScopeIds, false)} if (disabled) {if (! wasDisabled) { moveTeleport(n2, container, mainAnchor, internals, TeleportMoveTypes.TOGGLE) } } else { if ((n2.props && n2.props.to) ! == (n1.props && n1.props.to)) { const nextTarget = (n2.target = resolveTarget(n2.props, querySelector)) if (nextTarget) { moveTeleport( n2, nextTarget, null, internals, TeleportMoveTypes.TARGET_CHANGE ) } } else if (wasDisabled) { moveTeleport(n2, target, targetAnchor, internals, TeleportMoveTypes.TOGGLE) } }Copy the code

The specific process is as follows:

  1. Update sub-nodes, including full update and optimization update;
  2. If the new nodedisabledfortrue, while the old nodedisabledisfalseTo move the new node back to the main view nodemainAnchor;
  3. If the new nodedisabledforfalse.toIf the node changes, move the new node totoNode;
  4. If the new nodedisabledforfalse.toNodes do not change if the old nodedisabledistrue, the new node is moved from the main view node to the target nodetargetAnchor;

At this point, the update node is complete.

TeleportComponent Removal

We know that unmounting a component starts with the unmount method:

if (shapeFlag & ShapeFlags.TELEPORT) { ; (vnode.type as typeof TeleportImpl).remove( vnode, parentComponent, parentSuspense, optimized, internals, doRemove ) }Copy the code

If it is a Teleport component, TeleportImpl’s remove method is called directly.

remove( vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, optimized: boolean, { um: unmount, o: { remove: hostRemove } }: RendererInternals, doRemove: Boolean ) { const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode // 1. if (target) { hostRemove(targetAnchor!) } // an unmounted teleport should always remove its children if not disabled if (doRemove || ! isTeleportDisabled(props)) { hostRemove(anchor!) if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { for (let i = 0; i < (children as VNode[]).length; i++) { const child = (children as VNode[])[i] unmount( child, parentComponent, parentSuspense, true, !! child.dynamicChildren ) } } } }Copy the code

The specific process is as follows:

  1. If there is a target element, remove the target element first.
  2. Remove elements from the main view.
  3. Remove child node elements;

At this point, the node removal is complete.

A thought question

<template> <button class=" BTN "@click="changePosition"> </button> <teleport :to="to" :disabled="disabled" class="send_content">{{ showingString }}</div> </teleport> <sub-container /> </template>Copy the code

If in our case, the child component comes after the Teleport component, can the Teleport component display normally?