From 16cd8eee7839cc4613f17642bf37b39f7bdf1fce Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 25 Mar 2020 17:27:55 -0400 Subject: [PATCH] fix(portal): portal should always remove its children when unmounted --- packages/compiler-core/src/transforms/vIf.ts | 7 +++++-- .../__tests__/components/Portal.spec.ts | 17 +++++++++++++++++ .../__snapshots__/Portal.spec.ts.snap | 2 ++ packages/runtime-core/src/components/Portal.ts | 14 ++++++++++++++ packages/runtime-core/src/renderer.ts | 11 ++++++++++- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index cb0d6bd3875..ac60c4cd7d6 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 bdd50c7fd53..6c5c36bfda4 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 4a47a585826..3d4af4fdffd 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 46855ef752c..beac7e8ad06 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 e80a4976c13..929ea38fd20 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,