]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): fix render function + optimized slot edge case (#3523)
authorHcySunYang <HcySunYang@outlook.com>
Thu, 1 Apr 2021 23:28:58 +0000 (07:28 +0800)
committerGitHub <noreply@github.com>
Thu, 1 Apr 2021 23:28:58 +0000 (19:28 -0400)
fix #2893

Manually rendering the optimized slots should allow subsequent updates to exit the optimization mode correctly

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/src/componentSlots.ts
packages/runtime-core/src/renderer.ts

index 24f496caa79eee65cb781aac3fc4fc6fba9370b3..3c653281f1d8156628e76617ca6173ed0ff860d3 100644 (file)
@@ -15,7 +15,10 @@ import {
   defineComponent,
   withCtx,
   renderSlot,
-  onBeforeUnmount
+  onBeforeUnmount,
+  createTextVNode,
+  SetupContext,
+  createApp
 } from '@vue/runtime-test'
 import { PatchFlags, SlotFlags } from '@vue/shared'
 
@@ -517,4 +520,60 @@ describe('renderer: optimized mode', () => {
     expect(spyA).toHaveBeenCalledTimes(1)
     expect(spyB).toHaveBeenCalledTimes(1)
   })
+
+  // #2893
+  test('manually rendering the optimized slots should allow subsequent updates to exit the optimized mode correctly', async () => {
+    const state = ref(0)
+
+    const CompA = {
+      setup(props: any, { slots }: SetupContext) {
+        return () => {
+          return (
+            openBlock(),
+            createBlock('div', null, [renderSlot(slots, 'default')])
+          )
+        }
+      }
+    }
+
+    const Wrapper = {
+      setup(props: any, { slots }: SetupContext) {
+        // use the manually written render function to rendering the optimized slots,
+        // which should make subsequent updates exit the optimized mode correctly
+        return () => {
+          return slots.default!()[state.value]
+        }
+      }
+    }
+
+    const app = createApp({
+      setup() {
+        return () => {
+          return (
+            openBlock(),
+            createBlock(Wrapper, null, {
+              default: withCtx(() => [
+                createVNode(CompA, null, {
+                  default: withCtx(() => [createTextVNode('Hello')]),
+                  _: 1 /* STABLE */
+                }),
+                createVNode(CompA, null, {
+                  default: withCtx(() => [createTextVNode('World')]),
+                  _: 1 /* STABLE */
+                })
+              ]),
+              _: 1 /* STABLE */
+            })
+          )
+        }
+      }
+    })
+
+    app.mount(root)
+    expect(inner(root)).toBe('<div>Hello</div>')
+
+    state.value = 1
+    await nextTick()
+    expect(inner(root)).toBe('<div>World</div>')
+  })
 })
index 325f076fbf30170b90c693d69dcf23eca2b88c87..f1eacd8703d175df30beb81ced514f96ff20e55d 100644 (file)
@@ -130,7 +130,8 @@ export const initSlots = (
 
 export const updateSlots = (
   instance: ComponentInternalInstance,
-  children: VNodeNormalizedChildren
+  children: VNodeNormalizedChildren,
+  optimized: boolean
 ) => {
   const { vnode, slots } = instance
   let needDeletionCheck = true
@@ -143,7 +144,7 @@ export const updateSlots = (
         // Parent was HMR updated so slot content may have changed.
         // force update slots and mark instance for hmr as well
         extend(slots, children as Slots)
-      } else if (type === SlotFlags.STABLE) {
+      } else if (optimized && type === SlotFlags.STABLE) {
         // compiled AND stable.
         // no need to update, and skip stale slots removal.
         needDeletionCheck = false
@@ -151,6 +152,13 @@ export const updateSlots = (
         // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
         // normalization.
         extend(slots, children as Slots)
+        // #2893
+        // when rendering the optimized slots by manually written render function,
+        // we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
+        // i.e. let the `renderSlot` create the bailed Fragment
+        if (!optimized && type === SlotFlags.STABLE) {
+          delete slots._
+        }
       }
     } else {
       needDeletionCheck = !(children as RawSlots).$stable
index 15f86faa438e3d2ab4c35d58a3980006c5f60056..2c1b2bbde1507001d117e002185ae0d15721cd32 100644 (file)
@@ -1576,7 +1576,7 @@ function baseCreateRenderer(
     instance.vnode = nextVNode
     instance.next = null
     updateProps(instance, nextVNode.props, prevProps, optimized)
-    updateSlots(instance, nextVNode.children)
+    updateSlots(instance, nextVNode.children, optimized)
 
     pauseTracking()
     // props update may have triggered pre-flush watchers.