Przeglądaj źródła

fix(runtime-core): fix fragment update inside de-opt slots

fix #3881
Evan You 4 lat temu
rodzic
commit
5bce2ae723

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

@@ -20,7 +20,9 @@ import {
   onBeforeUnmount,
   createTextVNode,
   SetupContext,
-  createApp
+  createApp,
+  FunctionalComponent,
+  renderList
 } from '@vue/runtime-test'
 import { PatchFlags, SlotFlags } from '@vue/shared'
 import { SuspenseImpl } from '../src/components/Suspense'
@@ -821,4 +823,62 @@ describe('renderer: optimized mode', () => {
     await nextTick()
     expect(inner(root)).toBe('<div><div>true</div></div>')
   })
+
+  // #3881
+  // root cause: fragment inside a compiled slot passed to component which
+  // programmatically invokes the slot. The entire slot should de-opt but
+  // the fragment was incorretly put in optimized mode which causes it to skip
+  // updates for its inner components.
+  test('fragments inside programmatically invoked compiled slot should de-opt properly', async () => {
+    const Parent: FunctionalComponent = (_, { slots }) => slots.default!()
+    const Dummy = () => 'dummy'
+
+    const toggle = ref(true)
+    const force = ref(0)
+
+    const app = createApp({
+      render() {
+        if (!toggle.value) {
+          return null
+        }
+        return h(
+          Parent,
+          { n: force.value },
+          {
+            default: withCtx(
+              () => [
+                createVNode('ul', null, [
+                  (openBlock(),
+                  createBlock(
+                    Fragment,
+                    null,
+                    renderList(1, item => {
+                      return createVNode('li', null, [createVNode(Dummy)])
+                    }),
+                    64 /* STABLE_FRAGMENT */
+                  ))
+                ])
+              ],
+              undefined,
+              true
+            ),
+            _: 1 /* STABLE */
+          }
+        )
+      }
+    })
+
+    app.mount(root)
+
+    // force a patch
+    force.value++
+    await nextTick()
+    expect(inner(root)).toBe(`<ul><li>dummy</li></ul>`)
+
+    // unmount
+    toggle.value = false
+    await nextTick()
+    // should successfully unmount without error
+    expect(inner(root)).toBe(`<!---->`)
+  })
 })

+ 1 - 1
packages/runtime-core/src/renderer.ts

@@ -1170,7 +1170,7 @@ function baseCreateRenderer(
     const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!
 
     let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2
-    if (patchFlag > 0) {
+    if (dynamicChildren) {
       optimized = true
     }