Skip to content

Commit d343a0d

Browse files
committedJul 13, 2024··
fix(v-once): properly unmount v-once cached trees
close #5154 close #8809
1 parent 3107b57 commit d343a0d

File tree

8 files changed

+83
-14
lines changed

8 files changed

+83
-14
lines changed
 

Diff for: ‎packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ export function render(_ctx, _cache) {
1919
}"
2020
`;
2121

22-
exports[`compiler: codegen > CacheExpression w/ isVNode: true 1`] = `
22+
exports[`compiler: codegen > CacheExpression w/ isVOnce: true 1`] = `
2323
"
2424
export function render(_ctx, _cache) {
2525
return _cache[1] || (
2626
_setBlockTracking(-1),
27-
_cache[1] = foo,
27+
(_cache[1] = foo).cacheIndex = 1,
2828
_setBlockTracking(1),
2929
_cache[1]
3030
)

Diff for: ‎packages/compiler-core/__tests__/codegen.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ describe('compiler: codegen', () => {
437437
expect(code).toMatchSnapshot()
438438
})
439439

440-
test('CacheExpression w/ isVNode: true', () => {
440+
test('CacheExpression w/ isVOnce: true', () => {
441441
const { code } = generate(
442442
createRoot({
443443
cached: 1,
@@ -456,7 +456,7 @@ describe('compiler: codegen', () => {
456456
`
457457
_cache[1] || (
458458
_setBlockTracking(-1),
459-
_cache[1] = foo,
459+
(_cache[1] = foo).cacheIndex = 1,
460460
_setBlockTracking(1),
461461
_cache[1]
462462
)

Diff for: ‎packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ return function render(_ctx, _cache) {
99
1010
return _cache[0] || (
1111
_setBlockTracking(-1),
12-
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
12+
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
1313
_setBlockTracking(1),
1414
_cache[0]
1515
)
@@ -29,7 +29,7 @@ return function render(_ctx, _cache) {
2929
return (_openBlock(), _createElementBlock("div", null, [
3030
_cache[0] || (
3131
_setBlockTracking(-1),
32-
_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"]),
32+
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
3333
_setBlockTracking(1),
3434
_cache[0]
3535
)
@@ -48,7 +48,7 @@ return function render(_ctx, _cache) {
4848
return (_openBlock(), _createElementBlock("div", null, [
4949
_cache[0] || (
5050
_setBlockTracking(-1),
51-
_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"]),
51+
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
5252
_setBlockTracking(1),
5353
_cache[0]
5454
)
@@ -67,7 +67,7 @@ return function render(_ctx, _cache) {
6767
return (_openBlock(), _createElementBlock("div", null, [
6868
_cache[0] || (
6969
_setBlockTracking(-1),
70-
_cache[0] = _renderSlot($slots, "default"),
70+
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
7171
_setBlockTracking(1),
7272
_cache[0]
7373
)
@@ -86,7 +86,7 @@ return function render(_ctx, _cache) {
8686
return (_openBlock(), _createElementBlock("div", null, [
8787
_cache[0] || (
8888
_setBlockTracking(-1),
89-
_cache[0] = _createElementVNode("div"),
89+
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
9090
_setBlockTracking(1),
9191
_cache[0]
9292
)

Diff for: ‎packages/compiler-core/src/codegen.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1041,11 +1041,12 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
10411041
indent()
10421042
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
10431043
newline()
1044+
push(`(`)
10441045
}
10451046
push(`_cache[${node.index}] = `)
10461047
genNode(node.value, context)
10471048
if (node.isVOnce) {
1048-
push(`,`)
1049+
push(`).cacheIndex = ${node.index},`)
10491050
newline()
10501051
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
10511052
newline()

Diff for: ‎packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

+54
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
renderList,
2626
renderSlot,
2727
serialize,
28+
setBlockTracking,
2829
withCtx,
2930
} from '@vue/runtime-test'
3031
import { PatchFlags, SlotFlags } from '@vue/shared'
@@ -1178,4 +1179,57 @@ describe('renderer: optimized mode', () => {
11781179
await nextTick()
11791180
expect(inner(root)).toBe('<div><!--comment--><div>bar</div></div>')
11801181
})
1182+
1183+
test('should not take unmount children fast path if children contain cached nodes', async () => {
1184+
const show = ref(true)
1185+
const spyUnmounted = vi.fn()
1186+
1187+
const Child = {
1188+
setup() {
1189+
onUnmounted(spyUnmounted)
1190+
return () => createVNode('div', null, 'Child')
1191+
},
1192+
}
1193+
1194+
const app = createApp({
1195+
render(_: any, cache: any) {
1196+
return show.value
1197+
? (openBlock(),
1198+
createBlock('div', null, [
1199+
createVNode('div', null, [
1200+
cache[0] ||
1201+
(setBlockTracking(-1),
1202+
((cache[0] = createVNode('div', null, [
1203+
createVNode(Child),
1204+
])).cacheIndex = 0),
1205+
setBlockTracking(1),
1206+
cache[0]),
1207+
]),
1208+
]))
1209+
: createCommentVNode('v-if', true)
1210+
},
1211+
})
1212+
1213+
app.mount(root)
1214+
expect(inner(root)).toBe(
1215+
'<div><div><div><div>Child</div></div></div></div>',
1216+
)
1217+
1218+
show.value = false
1219+
await nextTick()
1220+
expect(inner(root)).toBe('<!--v-if-->')
1221+
expect(spyUnmounted).toHaveBeenCalledTimes(1)
1222+
1223+
show.value = true
1224+
await nextTick()
1225+
expect(inner(root)).toBe(
1226+
'<div><div><div><div>Child</div></div></div></div>',
1227+
)
1228+
1229+
// should unmount again, this verifies previous cache was properly cleared
1230+
show.value = false
1231+
await nextTick()
1232+
expect(inner(root)).toBe('<!--v-if-->')
1233+
expect(spyUnmounted).toHaveBeenCalledTimes(2)
1234+
})
11811235
})

Diff for: ‎packages/runtime-core/__tests__/vnode.spec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
Comment,
33
Fragment,
44
Text,
5+
type VNode,
56
cloneVNode,
67
createBlock,
78
createVNode,
@@ -633,7 +634,9 @@ describe('vnode', () => {
633634
setBlockTracking(1),
634635
vnode1,
635636
]))
636-
expect(vnode.dynamicChildren).toStrictEqual([])
637+
const expected: VNode['dynamicChildren'] = []
638+
expected.hasOnce = true
639+
expect(vnode.dynamicChildren).toStrictEqual(expected)
637640
})
638641
// #5657
639642
test('error of slot function execution should not affect block tracking', () => {

Diff for: ‎packages/runtime-core/src/renderer.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,12 @@ function baseCreateRenderer(
21642164
)
21652165
} else if (
21662166
dynamicChildren &&
2167+
// #5154
2168+
// when v-once is used inside a block, setBlockTracking(-1) marks the
2169+
// parent block with hasOnce: true
2170+
// so that it doesn't take the fast path during unmount - otherwise
2171+
// components nested in v-once are never unmounted.
2172+
!dynamicChildren.hasOnce &&
21672173
// #1153: fast path should not be taken for non-stable (v-for) fragments
21682174
(type !== Fragment ||
21692175
(patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))

Diff for: ‎packages/runtime-core/src/vnode.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export interface VNode<
226226
/**
227227
* @internal
228228
*/
229-
dynamicChildren: VNode[] | null
229+
dynamicChildren: (VNode[] & { hasOnce?: boolean }) | null
230230

231231
// application root node only
232232
appContext: AppContext | null
@@ -259,8 +259,8 @@ export interface VNode<
259259
// can divide a template into nested blocks, and within each block the node
260260
// structure would be stable. This allows us to skip most children diffing
261261
// and only worry about the dynamic nodes (indicated by patch flags).
262-
export const blockStack: (VNode[] | null)[] = []
263-
export let currentBlock: VNode[] | null = null
262+
export const blockStack: VNode['dynamicChildren'][] = []
263+
export let currentBlock: VNode['dynamicChildren'] = null
264264

265265
/**
266266
* Open a block.
@@ -311,6 +311,11 @@ export let isBlockTreeEnabled = 1
311311
*/
312312
export function setBlockTracking(value: number) {
313313
isBlockTreeEnabled += value
314+
if (value < 0 && currentBlock) {
315+
// mark current block so it doesn't take fast path and skip possible
316+
// nested components duriung unmount
317+
currentBlock.hasOnce = true
318+
}
314319
}
315320

316321
function setupBlock(vnode: VNode) {

0 commit comments

Comments
 (0)
Please sign in to comment.