]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense): fix suspense patching in optimized mode
authorEvan You <yyx990803@gmail.com>
Thu, 27 May 2021 20:32:31 +0000 (16:32 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 27 May 2021 20:32:31 +0000 (16:32 -0400)
fix #3828

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/src/components/Suspense.ts
packages/runtime-core/src/vnode.ts

index ef4f3ef8355614c6af89a85bc26f3e02108fbad0..bddd7d1d318e24df82fb62283a3be875a9e0a5f2 100644 (file)
@@ -23,6 +23,7 @@ import {
   createApp
 } from '@vue/runtime-test'
 import { PatchFlags, SlotFlags } from '@vue/shared'
+import { SuspenseImpl } from '../src/components/Suspense'
 
 describe('renderer: optimized mode', () => {
   let root: TestElement
@@ -784,4 +785,40 @@ describe('renderer: optimized mode', () => {
     await nextTick()
     expect(inner(root)).toBe('<div><div><span>loading</span></div></div>')
   })
+
+  // #3828
+  test('patch Suspense in optimized mode w/ nested dynamic nodes', async () => {
+    const show = ref(false)
+
+    const app = createApp({
+      render() {
+        return (
+          openBlock(),
+          createBlock(
+            Fragment,
+            null,
+            [
+              (openBlock(),
+              createBlock(SuspenseImpl, null, {
+                default: withCtx(() => [
+                  createVNode('div', null, [
+                    createVNode('div', null, show.value, PatchFlags.TEXT)
+                  ])
+                ]),
+                _: SlotFlags.STABLE
+              }))
+            ],
+            PatchFlags.STABLE_FRAGMENT
+          )
+        )
+      }
+    })
+
+    app.mount(root)
+    expect(inner(root)).toBe('<div><div>false</div></div>')
+
+    show.value = true
+    await nextTick()
+    expect(inner(root)).toBe('<div><div>true</div></div>')
+  })
 })
index 7510d78710ac33b0f80333b187538da757c87c0f..d5d544469f3c118c1c46d3361df1fada02c03697 100644 (file)
@@ -1,9 +1,12 @@
 import {
   VNode,
   normalizeVNode,
-  VNodeChild,
   VNodeProps,
-  isSameVNodeType
+  isSameVNodeType,
+  openBlock,
+  closeBlock,
+  currentBlock,
+  createVNode
 } from '../vnode'
 import { isFunction, isArray, ShapeFlags, toNumber } from '@vue/shared'
 import { ComponentInternalInstance, handleSetupResult } from '../component'
@@ -79,7 +82,8 @@ export const SuspenseImpl = {
     }
   },
   hydrate: hydrateSuspense,
-  create: createSuspenseBoundary
+  create: createSuspenseBoundary,
+  normalize: normalizeSuspenseChildren
 }
 
 // Force-casted public typing for h and TSX props inference
@@ -709,31 +713,34 @@ function hydrateSuspense(
   /* eslint-enable no-restricted-globals */
 }
 
-export function normalizeSuspenseChildren(
-  vnode: VNode
-): {
-  content: VNode
-  fallback: VNode
-} {
+function normalizeSuspenseChildren(vnode: VNode) {
   const { shapeFlag, children } = vnode
-  let content: VNode
-  let fallback: VNode
-  if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
-    content = normalizeSuspenseSlot((children as Slots).default)
-    fallback = normalizeSuspenseSlot((children as Slots).fallback)
-  } else {
-    content = normalizeSuspenseSlot(children as VNodeChild)
-    fallback = normalizeVNode(null)
-  }
-  return {
-    content,
-    fallback
-  }
+  const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
+  vnode.ssContent = normalizeSuspenseSlot(
+    isSlotChildren ? (children as Slots).default : children
+  )
+  vnode.ssFallback = isSlotChildren
+    ? normalizeSuspenseSlot((children as Slots).fallback)
+    : createVNode(Comment)
 }
 
 function normalizeSuspenseSlot(s: any) {
+  let block: VNode[] | null | undefined
   if (isFunction(s)) {
+    const isCompiledSlot = s._c
+    if (isCompiledSlot) {
+      // disableTracking: false
+      // allow block tracking for compiled slots
+      // (see ./componentRenderContext.ts)
+      s._d = false
+      openBlock()
+    }
     s = s()
+    if (isCompiledSlot) {
+      s._d = true
+      block = currentBlock
+      closeBlock()
+    }
   }
   if (isArray(s)) {
     const singleChild = filterSingleRoot(s)
@@ -742,7 +749,11 @@ function normalizeSuspenseSlot(s: any) {
     }
     s = singleChild
   }
-  return normalizeVNode(s)
+  s = normalizeVNode(s)
+  if (block) {
+    s.dynamicChildren = block.filter(c => c !== s)
+  }
+  return s
 }
 
 export function queueEffectWithSuspense(
index 99adae44c01874cec4f99b8543951995a9a21196..7109287d011b86c837e05f156fdf9e8a0bf1bbf9 100644 (file)
@@ -26,8 +26,7 @@ import { AppContext } from './apiCreateApp'
 import {
   SuspenseImpl,
   isSuspense,
-  SuspenseBoundary,
-  normalizeSuspenseChildren
+  SuspenseBoundary
 } from './components/Suspense'
 import { DirectiveBinding } from './directives'
 import { TransitionHooks } from './components/BaseTransition'
@@ -186,7 +185,7 @@ export interface VNode<
 // structure would be stable. This allows us to skip most children diffing
 // and only worry about the dynamic nodes (indicated by patch flags).
 export const blockStack: (VNode[] | null)[] = []
-let currentBlock: VNode[] | null = null
+export let currentBlock: VNode[] | null = null
 
 /**
  * Open a block.
@@ -452,9 +451,7 @@ function _createVNode(
 
   // normalize suspense children
   if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
-    const { content, fallback } = normalizeSuspenseChildren(vnode)
-    vnode.ssContent = content
-    vnode.ssFallback = fallback
+    ;(type as typeof SuspenseImpl).normalize(vnode)
   }
 
   if (