Skip to content

Commit 8655ced

Browse files
committedJul 16, 2024··
fix(teleport): skip teleported nodes when locating patch anchor
close #9071 close #9134 close #9313 Tests reused from #9313
1 parent 50ddafe commit 8655ced

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed
 

‎packages/runtime-core/__tests__/components/Teleport.spec.ts

+68-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
serializeInner,
1717
withDirectives,
1818
} from '@vue/runtime-test'
19-
import { Fragment, createVNode } from '../../src/vnode'
19+
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
2020
import { compile, render as domRender } from 'vue'
2121

2222
describe('renderer: teleport', () => {
@@ -553,4 +553,71 @@ describe('renderer: teleport', () => {
553553
`"<div>teleported</div>"`,
554554
)
555555
})
556+
557+
//#9071
558+
test('toggle sibling node inside target node', async () => {
559+
const root = document.createElement('div')
560+
const show = ref(false)
561+
const App = defineComponent({
562+
setup() {
563+
return () => {
564+
return show.value
565+
? h(Teleport, { to: root }, [h('div', 'teleported')])
566+
: h('div', 'foo')
567+
}
568+
},
569+
})
570+
571+
domRender(h(App), root)
572+
expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
573+
574+
show.value = true
575+
await nextTick()
576+
577+
expect(root.innerHTML).toMatchInlineSnapshot(
578+
'"<!--teleport start--><!--teleport end--><div>teleported</div>"',
579+
)
580+
581+
show.value = false
582+
await nextTick()
583+
584+
expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
585+
})
586+
587+
test('unmount previous sibling node inside target node', async () => {
588+
const root = document.createElement('div')
589+
const parentShow = ref(false)
590+
const childShow = ref(true)
591+
592+
const Comp = {
593+
setup() {
594+
return () => h(Teleport, { to: root }, [h('div', 'foo')])
595+
},
596+
}
597+
598+
const App = defineComponent({
599+
setup() {
600+
return () => {
601+
return parentShow.value
602+
? h(Fragment, { key: 0 }, [
603+
childShow.value ? h(Comp) : createCommentVNode('v-if'),
604+
])
605+
: createCommentVNode('v-if')
606+
}
607+
},
608+
})
609+
610+
domRender(h(App), root)
611+
expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
612+
613+
parentShow.value = true
614+
await nextTick()
615+
expect(root.innerHTML).toMatchInlineSnapshot(
616+
'"<!--teleport start--><!--teleport end--><div>foo</div>"',
617+
)
618+
619+
parentShow.value = false
620+
await nextTick()
621+
expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
622+
})
556623
})

‎packages/runtime-core/src/components/Teleport.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface TeleportProps {
2121
disabled?: boolean
2222
}
2323

24+
export const TeleportEndKey = Symbol('_vte')
25+
2426
export const isTeleport = (type: any): boolean => type.__isTeleport
2527

2628
const isTeleportDisabled = (props: VNode['props']): boolean =>
@@ -105,11 +107,16 @@ export const TeleportImpl = {
105107
const mainAnchor = (n2.anchor = __DEV__
106108
? createComment('teleport end')
107109
: createText(''))
108-
insert(placeholder, container, anchor)
109-
insert(mainAnchor, container, anchor)
110110
const target = (n2.target = resolveTarget(n2.props, querySelector))
111+
const targetStart = (n2.targetStart = createText(''))
111112
const targetAnchor = (n2.targetAnchor = createText(''))
113+
insert(placeholder, container, anchor)
114+
insert(mainAnchor, container, anchor)
115+
// attach a special property so we can skip teleported content in
116+
// renderer's nextSibling search
117+
targetStart[TeleportEndKey] = targetAnchor
112118
if (target) {
119+
insert(targetStart, target)
113120
insert(targetAnchor, target)
114121
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
115122
if (namespace === 'svg' || isTargetSVG(target)) {
@@ -146,6 +153,7 @@ export const TeleportImpl = {
146153
} else {
147154
// update content
148155
n2.el = n1.el
156+
n2.targetStart = n1.targetStart
149157
const mainAnchor = (n2.anchor = n1.anchor)!
150158
const target = (n2.target = n1.target)!
151159
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
@@ -253,9 +261,18 @@ export const TeleportImpl = {
253261
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
254262
doRemove: boolean,
255263
) {
256-
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
264+
const {
265+
shapeFlag,
266+
children,
267+
anchor,
268+
targetStart,
269+
targetAnchor,
270+
target,
271+
props,
272+
} = vnode
257273

258274
if (target) {
275+
hostRemove(targetStart!)
259276
hostRemove(targetAnchor!)
260277
}
261278

‎packages/runtime-core/src/renderer.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ import {
5858
type SuspenseImpl,
5959
queueEffectWithSuspense,
6060
} from './components/Suspense'
61-
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
61+
import {
62+
TeleportEndKey,
63+
type TeleportImpl,
64+
type TeleportVNode,
65+
} from './components/Teleport'
6266
import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
6367
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
6468
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
@@ -140,7 +144,7 @@ export interface RendererOptions<
140144
// functions provided via options, so the internal constraint is really just
141145
// a generic object.
142146
export interface RendererNode {
143-
[key: string]: any
147+
[key: string | symbol]: any
144148
}
145149

146150
export interface RendererElement extends RendererNode {}
@@ -2368,7 +2372,12 @@ function baseCreateRenderer(
23682372
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
23692373
return vnode.suspense!.next()
23702374
}
2371-
return hostNextSibling((vnode.anchor || vnode.el)!)
2375+
const el = hostNextSibling((vnode.anchor || vnode.el)!)
2376+
// #9071, #9313
2377+
// teleported content can mess up nextSibling searches during patch so
2378+
// we need to skip them during nextSibling search
2379+
const teleportEnd = el && el[TeleportEndKey]
2380+
return teleportEnd ? hostNextSibling(teleportEnd) : el
23722381
}
23732382

23742383
let isFlushing = false

‎packages/runtime-core/src/vnode.ts

+3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export interface VNode<
198198
el: HostNode | null
199199
anchor: HostNode | null // fragment anchor
200200
target: HostElement | null // teleport target
201+
targetStart: HostNode | null // teleport target start anchor
201202
targetAnchor: HostNode | null // teleport target anchor
202203
/**
203204
* number of elements contained in a static vnode
@@ -477,6 +478,7 @@ function createBaseVNode(
477478
el: null,
478479
anchor: null,
479480
target: null,
481+
targetStart: null,
480482
targetAnchor: null,
481483
staticCount: 0,
482484
shapeFlag,
@@ -677,6 +679,7 @@ export function cloneVNode<T, U>(
677679
? (children as VNode[]).map(deepCloneVNode)
678680
: children,
679681
target: vnode.target,
682+
targetStart: vnode.targetStart,
680683
targetAnchor: vnode.targetAnchor,
681684
staticCount: vnode.staticCount,
682685
shapeFlag: vnode.shapeFlag,

0 commit comments

Comments
 (0)
Please sign in to comment.