preface
Knowing what it is and what it is, a good engineer must not only be adept at using the framework, but also understand how it is implemented underneath. This paper mainly explores the implementation principle of built-in TelePort component in Vue3 source code.
A common scenario for Teleport components is to create a component that contains full-screen mode. In most cases, you want the logic of the modal box to exist in the component, but quick positioning of the modal box is difficult to solve with CSS, or requires changing the component composition. If you want to know how to use the Vue3 Teleport, it is described in detail on the website.
Realize the principle of
The Teleport component is implemented as an object in the source code and provides several methods externally, the most important being two process and remove methods. The code is as follows:
// packages/runtime-core/src/components/Teleport.ts
const Teleport = {
__isTeleport: true.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals) {
if (n1 == null) {
// Create logic
} else {
// Update logic}},remove(vnode, { r: remove, o: { remove: hostRemove } }) {
// Delete logic
},
move: moveTeleport,
hydrate: hydrateTeleport
}
Copy the code
In the code above, the process method is responsible for creating and updating the component logic, and the remove method is responsible for removing the component logic.
Components to create
The template rendering will be converted to the Render function, which will call the createBlock method, and the final component creation will call patch.
// packages/runtime-core/src/renderer.ts
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false:!!!!! n2.dynamicChildren) = >{...// patching & not same type, unmount old tree
if(n1 && ! isSameVNodeType(n1, n2)) {// If there are old and new nodes, and the types of the old and new nodes are different, the old node is destroyed
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
// Process text nodes
break
case Comment:
// Process the comment node
break
case Static:
// Handle static nodes
break
case Fragment:
/ / processing fragments
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// Handle normal DOM elements
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// Process components
} else if (shapeFlag & ShapeFlags.TELEPORT) {
/ / processing TELEPORT
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
/ / deal with SUSPENSE
} else if (__DEV__) {
warn('Invalid VNode type:'.type.` (The ${typeof type}) `)}}... }Copy the code
If type is a Teleport component, its process method will be executed. If type is a Teleport component, its process method will execute.
// packages/runtime-core/src/components/Teleport.ts
process(
n1: TeleportVNode | null,
n2: TeleportVNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
internals: RendererInternals
) {
const {
mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
o: { insert, querySelector, createText, createComment }
} = internals
const disabled = isTeleportDisabled(n2.props)
let { shapeFlag, children, dynamicChildren } = n2
...
if (n1 == null) {
// insert anchors in the main view
// Insert a comment node or blank text node in the main view
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)
// Get the target DOM node to move
const target = (n2.target = resolveTarget(n2.props, querySelector))
const targetAnchor = (n2.targetAnchor = createText(' '))
if (target) {
insert(targetAnchor, target)
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
isSVG = isSVG || isTargetSVG(target)
} else if(__DEV__ && ! disabled) {// Target cannot be found
warn('Invalid Teleport target on mount:', target, ` (The ${typeof target}) `)}const mount = (container: RendererElement, anchor: RendererNode) = > {
// Mount the child node
if(shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(...) }}if (disabled) {
// The disabled condition is mounted in its original position
mount(container, mainAnchor)
} else if (target) {
// Mount to target
mount(target, targetAnchor)
}
}
}
Copy the code
The Teleport component creation part is mainly divided into three steps. The first step is to insert the comment node or blank text node in the main view, the second step is to obtain the target element node, and the third step is to mount the child node to the target element and insert the child node of the Teleport component into the target element.
Component updates
// packages/runtime-core/src/components/Teleport.ts
process(
n1: TeleportVNode | null,
n2: TeleportVNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
internals: RendererInternals
) {
const {
mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
o: { insert, querySelector, createText, createComment }
} = internals
const disabled = isTeleportDisabled(n2.props)
let { shapeFlag, children, dynamicChildren } = n2
/ / # 3302
// HMR updated, force full diff
if (__DEV__ && isHmrUpdating) {
optimized = false
dynamicChildren = null
}
if (n1 == null) {
// Component creation
} else {
// update content
n2.el = n1.el
const mainAnchor = (n2.anchor = n1.anchor)!
const target = (n2.target = n1.target)!
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
// Was disabled before
const wasDisabled = isTeleportDisabled(n1.props)
const currentContainer = wasDisabled ? container : target
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
isSVG = isSVG || isTargetSVG(target)
// Update the child node
if (dynamicChildren) {
// fast path when the teleport happens to be a block rootpatchBlockChildren( 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) {// Move the child node back to the main container
moveTeleport(
n2,
container,
mainAnchor,
internals,
TeleportMoveTypes.TOGGLE
)
}
} else {
// The target element changes
if((n2.props && n2.props.to) ! == (n1.props && n1.props.to)) {const nextTarget = (n2.target = resolveTarget(
n2.props,
querySelector
))
if (nextTarget) {
// enabled -> disabled
// Move to the new target element
moveTeleport(
n2,
nextTarget,
null,
internals,
TeleportMoveTypes.TARGET_CHANGE
)
} else if (__DEV__) {
warn(
'Invalid Teleport target on update:',
target,
` (The ${typeof target}) `)}}else if (wasDisabled) {
// disabled -> enabled
// Move to the target element
moveTeleport(
n2,
target,
targetAnchor,
internals,
TeleportMoveTypes.TOGGLE
)
}
}
}
}
Copy the code
A Teleport component update does just a few things: updates child nodes, handles changes in the disabled property, and handles changes in the TO property.
The first step is to update the child nodes of the Teleport component, which can be divided into optimized updates and normal full comparison updates.
The new node of the Teleport component is configured with the disabled property. If the disabled property of the new node is true and the disabled property of the old node is false, We need to move the Teleport child from inside the target element back inside the main view.
If disabled is false, the target element is changed by the to attribute. If so, Teleport’s child nodes are moved inside the new target. If the target element does not change, the disabled of the old node is true, if so, Teleport’s children are moved from inside the main view to inside the target element.
Components to remove
The unmount method is executed when the component is removed, and its internals determine that if the component being removed is a Teleport component, the remove method of the component is executed.
// packages/runtime-core/src/renderer.ts
const unmount: UnmountFn = (
vnode,
parentComponent,
parentSuspense,
doRemove = false,
optimized = false
) = > {
const {
type,
props,
ref,
children,
dynamicChildren,
shapeFlag,
patchFlag,
dirs
} = vnode
...
if(shapeFlag & ShapeFlags.TELEPORT) { ; (vnode.typeas typeof TeleportImpl).remove(
vnode,
parentComponent,
parentSuspense,
optimized,
internals,
doRemove
)
}
...
}
Copy the code
If ShapeFlags is a Teleport component, its remove method is executed. If ShapeFlags is a Teleport component, its remove method is executed.
// packages/runtime-core/src/components/Teleport.ts
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
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
Teleport’s remove method is very simple to implement, first remove the anchor point Teleport start annotation node via hostRemove, and then unmount the child nodes traversing Teleport.
After Teleport’s unmount method is executed, the hostRemove method is executed to remove the element Teleport End annotation node from the main view of Teleport. At this point, the Teleport component is removed.
Write in the last
Finally, the implementation principle of Teleport component is summarized:
- Teleport is called when the component is rendered
patch
Method,patch
The shapeFlag method determines that if shapeFlag is a Teleport component, it will be calledprocess
Methods.process
Method contains the logic for Teleport component creation and component update. - Teleport component creation first inserts a comment node or blank text node in the main view, then obtains the target element node, and finally calls mount to create child nodes and inserts Teleport component child nodes into the target element.
- Teleport component updates first update child nodes to handle changes in the disabled property and to property.
- Finally the Teleport component mount is called
unmount
Method to determine if shapeFlag is a Teleport component, its remove method will be executed. - The remove method calls the hostRemove method to remove the text node, then loops through the child node and calls the unmount method to mount the child node.
PS: To quickly build your own front-end static blog, check out Vuepress Quick Build Blog – a blog topic you deserve.
Reference documentation
Vue. Js 3.0 core source code internal reference