diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index cb0d6bd38..ac60c4cd7 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -26,7 +26,8 @@ import {
CREATE_BLOCK,
FRAGMENT,
CREATE_COMMENT,
- OPEN_BLOCK
+ OPEN_BLOCK,
+ PORTAL
} from '../runtimeHelpers'
import { injectProp } from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
@@ -216,7 +217,9 @@ function createChildrenCodegenNode(
vnodeCall.type === NodeTypes.VNODE_CALL &&
// component vnodes are always tracked and its children are
// compiled into slots so no need to make it a block
- (firstChild as ElementNode).tagType !== ElementTypes.COMPONENT
+ ((firstChild as ElementNode).tagType !== ElementTypes.COMPONENT ||
+ // portal has component type but isn't always tracked
+ vnodeCall.tag === PORTAL)
) {
vnodeCall.isBlock = true
helper(OPEN_BLOCK)
diff --git a/packages/runtime-core/__tests__/components/Portal.spec.ts b/packages/runtime-core/__tests__/components/Portal.spec.ts
index bdd50c7fd..6c5c36bfd 100644
--- a/packages/runtime-core/__tests__/components/Portal.spec.ts
+++ b/packages/runtime-core/__tests__/components/Portal.spec.ts
@@ -76,4 +76,21 @@ describe('renderer: portal', () => {
expect(serializeInner(target)).toMatchSnapshot()
})
+
+ test('should remove children when unmounted', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+
+ const Comp = defineComponent(() => () => [
+ h(Portal, { target }, h('div', 'teleported')),
+ h('div', 'root')
+ ])
+ render(h(Comp), root)
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"
teleported
"`
+ )
+
+ render(null, root)
+ expect(serializeInner(target)).toBe('')
+ })
})
diff --git a/packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap b/packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap
index 4a47a5858..3d4af4fdf 100644
--- a/packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap
+++ b/packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`renderer: portal should remove children when unmounted 1`] = `"teleported
"`;
+
exports[`renderer: portal should update children 1`] = `"teleported
"`;
exports[`renderer: portal should update children 2`] = `""`;
diff --git a/packages/runtime-core/src/components/Portal.ts b/packages/runtime-core/src/components/Portal.ts
index 46855ef75..beac7e8ad 100644
--- a/packages/runtime-core/src/components/Portal.ts
+++ b/packages/runtime-core/src/components/Portal.ts
@@ -113,6 +113,20 @@ export const PortalImpl = {
}
}
}
+ },
+
+ remove(
+ vnode: VNode,
+ { r: remove, o: { setElementText } }: RendererInternals
+ ) {
+ const { target, shapeFlag, children } = vnode
+ if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
+ setElementText(target!, '')
+ } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
+ for (let i = 0; i < (children as VNode[]).length; i++) {
+ remove((children as VNode[])[i])
+ }
+ }
}
}
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index e80a4976c..929ea38fd 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -139,6 +139,7 @@ export interface RendererInternals<
> {
p: PatchFn
um: UnmountFn
+ r: RemoveFn
m: MoveFn
mt: MountComponentFn
mc: MountChildrenFn
@@ -210,6 +211,8 @@ type UnmountFn = (
doRemove?: boolean
) => void
+type RemoveFn = (vnode: VNode) => void
+
type UnmountChildrenFn = (
children: VNode[],
parentComponent: ComponentInternalInstance | null,
@@ -1688,6 +1691,11 @@ function baseCreateRenderer(
unmountChildren(children as VNode[], parentComponent, parentSuspense)
}
+ // an unmounted portal should always remove its children
+ if (shapeFlag & ShapeFlags.PORTAL) {
+ ;(vnode.type as typeof PortalImpl).remove(vnode, internals)
+ }
+
if (doRemove) {
remove(vnode)
}
@@ -1702,7 +1710,7 @@ function baseCreateRenderer(
}
}
- const remove = (vnode: VNode) => {
+ const remove: RemoveFn = vnode => {
const { type, el, anchor, transition } = vnode
if (type === Fragment) {
removeFragment(el!, anchor!)
@@ -1888,6 +1896,7 @@ function baseCreateRenderer(
p: patch,
um: unmount,
m: move,
+ r: remove,
mt: mountComponent,
mc: mountChildren,
pc: patchChildren,