]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(v-once): setting hasOnce to current block only when in v-once (#12374)
authoredison <daiwei521@126.com>
Thu, 14 Nov 2024 06:53:55 +0000 (14:53 +0800)
committerGitHub <noreply@github.com>
Thu, 14 Nov 2024 06:53:55 +0000 (14:53 +0800)
close #12371

packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/vOnce.ts
packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/vnode.ts

index 3d13c4066d9fb1bc4796fd3471c50b99b6415658..6660865a5239ae13298578159f5be67e5579ef5b 100644 (file)
@@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
     const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
 
     return _cache[0] || (
-      _setBlockTracking(-1),
+      _setBlockTracking(-1, true),
       (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
       _setBlockTracking(1),
       _cache[0]
@@ -28,7 +28,7 @@ return function render(_ctx, _cache) {
 
     return (_openBlock(), _createElementBlock("div", null, [
       _cache[0] || (
-        _setBlockTracking(-1),
+        _setBlockTracking(-1, true),
         (_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
         _setBlockTracking(1),
         _cache[0]
@@ -47,7 +47,7 @@ return function render(_ctx, _cache) {
 
     return (_openBlock(), _createElementBlock("div", null, [
       _cache[0] || (
-        _setBlockTracking(-1),
+        _setBlockTracking(-1, true),
         (_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
         _setBlockTracking(1),
         _cache[0]
@@ -66,7 +66,7 @@ return function render(_ctx, _cache) {
 
     return (_openBlock(), _createElementBlock("div", null, [
       _cache[0] || (
-        _setBlockTracking(-1),
+        _setBlockTracking(-1, true),
         (_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
         _setBlockTracking(1),
         _cache[0]
@@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
 
     return (_openBlock(), _createElementBlock("div", null, [
       _cache[0] || (
-        _setBlockTracking(-1),
+        _setBlockTracking(-1, true),
         (_cache[0] = _createElementVNode("div")).cacheIndex = 0,
         _setBlockTracking(1),
         _cache[0]
index cfd5fee2569b358c103307f6a7afcd5ed0e8aa4c..2d6df9d90106941708f3398cda3daf64c667ba55 100644 (file)
@@ -418,6 +418,7 @@ export interface CacheExpression extends Node {
   index: number
   value: JSChildNode
   needPauseTracking: boolean
+  inVOnce: boolean
   needArraySpread: boolean
 }
 
@@ -774,12 +775,14 @@ export function createCacheExpression(
   index: number,
   value: JSChildNode,
   needPauseTracking: boolean = false,
+  inVOnce: boolean = false,
 ): CacheExpression {
   return {
     type: NodeTypes.JS_CACHE_EXPRESSION,
     index,
     value,
     needPauseTracking: needPauseTracking,
+    inVOnce,
     needArraySpread: false,
     loc: locStub,
   }
index 6b6f24b3a30ed97cf053023b57ee8215024a52dd..70116cfb61a9f7d6e662b64c3df79f08c7f50b23 100644 (file)
@@ -1017,7 +1017,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
   push(`_cache[${node.index}] || (`)
   if (needPauseTracking) {
     indent()
-    push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
+    push(`${helper(SET_BLOCK_TRACKING)}(-1`)
+    if (node.inVOnce) push(`, true`)
+    push(`),`)
     newline()
     push(`(`)
   }
index b47b6b8d4088295a15dc61c0ddc991a5242423af..aeb96cc2b4adc1cb0b07a85f6caed251fd4daa2c 100644 (file)
@@ -116,7 +116,7 @@ export interface TransformContext
   addIdentifiers(exp: ExpressionNode | string): void
   removeIdentifiers(exp: ExpressionNode | string): void
   hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
-  cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
+  cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression
   constantCache: WeakMap<TemplateChildNode, ConstantTypes>
 
   // 2.x Compat only
@@ -297,11 +297,12 @@ export function createTransformContext(
       identifier.hoisted = exp
       return identifier
     },
-    cache(exp, isVNode = false) {
+    cache(exp, isVNode = false, inVOnce = false) {
       const cacheExp = createCacheExpression(
         context.cached.length,
         exp,
         isVNode,
+        inVOnce,
       )
       context.cached.push(cacheExp)
       return cacheExp
index 483b98da9610d105ad61168e439a5215f49d3676..685da59ccf0bff6f3a7b5c0b4b4541f5149ddfec 100644 (file)
@@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => {
       context.inVOnce = false
       const cur = context.currentNode as ElementNode | IfNode | ForNode
       if (cur.codegenNode) {
-        cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
+        cur.codegenNode = context.cache(
+          cur.codegenNode,
+          true /* isVNode */,
+          true /* inVOnce */,
+        )
       }
     }
   }
index 2658f40718fea53a581c4b643bd9066652cbb09c..958c1274806c6b93563ffee36355282b98e62fa2 100644 (file)
@@ -17,6 +17,7 @@ import {
   serializeInner as inner,
   nextTick,
   nodeOps,
+  onBeforeMount,
   onBeforeUnmount,
   onUnmounted,
   openBlock,
@@ -1199,7 +1200,7 @@ describe('renderer: optimized mode', () => {
             createBlock('div', null, [
               createVNode('div', null, [
                 cache[0] ||
-                  (setBlockTracking(-1),
+                  (setBlockTracking(-1, true),
                   ((cache[0] = createVNode('div', null, [
                     createVNode(Child),
                   ])).cacheIndex = 0),
@@ -1233,4 +1234,64 @@ describe('renderer: optimized mode', () => {
     expect(inner(root)).toBe('<!--v-if-->')
     expect(spyUnmounted).toHaveBeenCalledTimes(2)
   })
+
+  // #12371
+  test('unmount children when the user calls a compiled slot', async () => {
+    const beforeMountSpy = vi.fn()
+    const beforeUnmountSpy = vi.fn()
+
+    const Child = {
+      setup() {
+        onBeforeMount(beforeMountSpy)
+        onBeforeUnmount(beforeUnmountSpy)
+        return () => 'child'
+      },
+    }
+
+    const Wrapper = {
+      setup(_: any, { slots }: SetupContext) {
+        return () => (
+          openBlock(),
+          createElementBlock('section', null, [
+            (openBlock(),
+            createElementBlock('div', { key: 1 }, [
+              createTextVNode(slots.header!() ? 'foo' : 'bar', 1 /* TEXT */),
+              renderSlot(slots, 'content'),
+            ])),
+          ])
+        )
+      },
+    }
+
+    const show = ref(false)
+    const app = createApp({
+      render() {
+        return show.value
+          ? (openBlock(),
+            createBlock(Wrapper, null, {
+              header: withCtx(() => [createVNode({})]),
+              content: withCtx(() => [createVNode(Child)]),
+              _: 1,
+            }))
+          : createCommentVNode('v-if', true)
+      },
+    })
+
+    app.mount(root)
+    expect(inner(root)).toMatchInlineSnapshot(`"<!--v-if-->"`)
+    expect(beforeMountSpy).toHaveBeenCalledTimes(0)
+    expect(beforeUnmountSpy).toHaveBeenCalledTimes(0)
+
+    show.value = true
+    await nextTick()
+    expect(inner(root)).toMatchInlineSnapshot(
+      `"<section><div>foochild</div></section>"`,
+    )
+    expect(beforeMountSpy).toHaveBeenCalledTimes(1)
+
+    show.value = false
+    await nextTick()
+    expect(inner(root)).toBe('<!--v-if-->')
+    expect(beforeUnmountSpy).toHaveBeenCalledTimes(1)
+  })
 })
index 2e0eee1f2802cce6b3e57b25f93efc5b129b138f..a7f6a2d5684d5bcb1b80350b901dafce5c28ba44 100644 (file)
@@ -629,7 +629,7 @@ describe('vnode', () => {
       const vnode =
         (openBlock(),
         createBlock('div', null, [
-          setBlockTracking(-1),
+          setBlockTracking(-1, true),
           (vnode1 = (openBlock(), createBlock('div'))),
           setBlockTracking(1),
           vnode1,
index 30200789be8f7bcefb13eb131e393ef0a0b886ce..a8c5340cd1fe167840f700e34a2a25b1909eb048 100644 (file)
@@ -301,7 +301,7 @@ export let isBlockTreeEnabled = 1
  *
  * ``` js
  * _cache[1] || (
- *   setBlockTracking(-1),
+ *   setBlockTracking(-1, true),
  *   _cache[1] = createVNode(...),
  *   setBlockTracking(1),
  *   _cache[1]
@@ -310,11 +310,11 @@ export let isBlockTreeEnabled = 1
  *
  * @private
  */
-export function setBlockTracking(value: number): void {
+export function setBlockTracking(value: number, inVOnce = false): void {
   isBlockTreeEnabled += value
-  if (value < 0 && currentBlock) {
+  if (value < 0 && currentBlock && inVOnce) {
     // mark current block so it doesn't take fast path and skip possible
-    // nested components duriung unmount
+    // nested components during unmount
     currentBlock.hasOnce = true
   }
 }