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.
Teleport
Use of components
Teleport
Components 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 movedon the body, not where the component’s template is.
Teleport takes two arguments:
to
For the position to be moved, it can be a selector or a DOM node;disabled
If it istrue
, the content does not move,disabled
If it isfalse
,Teleport
The wrapped element node is moved toto
Under the node of
Example: Implement something in theComponent in the template.In the template of the child component 和 bodySwitch between.
- Child componentsThere is a
#teleport1
node
<! Vue --> <template> <div id="teleport1"> <h4> </h4> </div> </template>Copy the code
- APP componentcontainsChild componentsThere is a button
button
Toggle 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.
Teleport
Component implementation principles
Teleport
Component 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:
- Create a node
mainAnchor
The development environment is oneComment nodeThe release environment is oneEmpty text nodeTo create thismainAnchor
The node is mounted under the DOM node corresponding to the parent component.- use
querySelector
findTeleport
componentto
Property to specify the nodetarget
Target node, and then intargetAnchor
Create an empty text node under the node as the anchor node;- if
Teleport
componentdisabled
Attribute values fortrue
That will beTeleport
The child nodes of the component are mounted onmainAnchor
H, ifdisabled
Attribute values forfalse
That will beTeleport
The child nodes of the component are mounted on the target nodetargetAnchor
.
Teleport
Component 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:
- Update sub-nodes, including full update and optimization update;
- If the new node
disabled
fortrue
, while the old nodedisabled
isfalse
To move the new node back to the main view nodemainAnchor
;- If the new node
disabled
forfalse
.to
If the node changes, move the new node toto
Node;- If the new node
disabled
forfalse
.to
Nodes do not change if the old nodedisabled
istrue
, the new node is moved from the main view node to the target nodetargetAnchor
;At this point, the update node is complete.
Teleport
Component 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:
- If there is a target element, remove the target element first.
- Remove elements from the main view.
- 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?