Procházet zdrojové kódy

fix(v-once): setting hasOnce to current block only when in v-once (#12374)

close #12371
edison před 1 rokem
rodič
revize
37300fc261

+ 5 - 5
packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

@@ -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]

+ 3 - 0
packages/compiler-core/src/ast.ts

@@ -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,
   }

+ 3 - 1
packages/compiler-core/src/codegen.ts

@@ -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(`(`)
   }

+ 3 - 2
packages/compiler-core/src/transform.ts

@@ -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

+ 5 - 1
packages/compiler-core/src/transforms/vOnce.ts

@@ -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 */,
+        )
       }
     }
   }

+ 62 - 1
packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

@@ -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)
+  })
 })

+ 1 - 1
packages/runtime-core/__tests__/vnode.spec.ts

@@ -629,7 +629,7 @@ describe('vnode', () => {
       const vnode =
         (openBlock(),
         createBlock('div', null, [
-          setBlockTracking(-1),
+          setBlockTracking(-1, true),
           (vnode1 = (openBlock(), createBlock('div'))),
           setBlockTracking(1),
           vnode1,

+ 4 - 4
packages/runtime-core/src/vnode.ts

@@ -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
   }
 }