]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): enable block tracking when normalizing plain element with slot...
authorHcySunYang <HcySunYang@outlook.com>
Tue, 1 Sep 2020 16:38:47 +0000 (00:38 +0800)
committerGitHub <noreply@github.com>
Tue, 1 Sep 2020 16:38:47 +0000 (12:38 -0400)
fix #1980

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/helpers/renderSlot.ts
packages/runtime-core/src/helpers/withRenderContext.ts
packages/runtime-core/src/vnode.ts

index 26fee92f44d0f286f1f6773e4433e5cdd2c46997..73e566c930e9bc59359173943d4ed593a724aec6 100644 (file)
@@ -11,9 +11,12 @@ import {
   serializeInner as inner,
   VNode,
   ref,
-  nextTick
+  nextTick,
+  defineComponent,
+  withCtx,
+  renderSlot
 } from '@vue/runtime-test'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlags, SlotFlags } from '@vue/shared'
 
 describe('renderer: optimized mode', () => {
   let root: TestElement
@@ -398,4 +401,52 @@ describe('renderer: optimized mode', () => {
     expect(inner(root)).toBe('<div><i>bar</i></div>')
     expect(block!.dynamicChildren).toBe(null)
   })
+
+  // #1980
+  test('dynamicChildren should be tracked correctly when normalizing slots to plain children', async () => {
+    let block: VNode
+    const Comp = defineComponent({
+      setup(_props, { slots }) {
+        return () => {
+          const vnode = (openBlock(),
+          (block = createBlock('div', null, {
+            default: withCtx(() => [renderSlot(slots, 'default')]),
+            _: SlotFlags.FORWARDED
+          })))
+
+          return vnode
+        }
+      }
+    })
+
+    const foo = ref(0)
+    const App = {
+      setup() {
+        return () => {
+          return createVNode(Comp, null, {
+            default: withCtx(() => [
+              createVNode('p', null, foo.value, PatchFlags.TEXT)
+            ]),
+            // Indicates that this is a stable slot to avoid bail out
+            _: SlotFlags.STABLE
+          })
+        }
+      }
+    }
+
+    render(h(App), root)
+    expect(inner(root)).toBe('<div><p>0</p></div>')
+    expect(block!.dynamicChildren!.length).toBe(1)
+    expect(block!.dynamicChildren![0].type).toBe(Fragment)
+    expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
+    expect(
+      serialize(block!.dynamicChildren![0].dynamicChildren![0]
+        .el as TestElement)
+    ).toBe('<p>0</p>')
+
+    foo.value++
+    await nextTick()
+
+    expect(inner(root)).toBe('<div><p>1</p></div>')
+  })
 })
index ad3575610a92dc11e725f924649899087717153c..2ee1872afaac4eaca8dcc1eb4a4a81b23a8d6a06 100644 (file)
@@ -130,10 +130,10 @@ describe('vnode', () => {
     })
 
     test('object', () => {
-      const vnode = createVNode('p', null, { foo: 'foo' })
+      const vnode = createVNode({}, null, { foo: 'foo' })
       expect(vnode.children).toMatchObject({ foo: 'foo' })
       expect(vnode.shapeFlag).toBe(
-        ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
+        ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.SLOTS_CHILDREN
       )
     })
 
index be6dbba4a4275ed29684deba0166a0ee04f6430e..2e43c628678bbcc450b57f265ed23a096ae4a416 100644 (file)
@@ -11,6 +11,8 @@ import { PatchFlags, SlotFlags } from '@vue/shared'
 import { warn } from '../warning'
 
 export let isRenderingCompiledSlot = 0
+export const setCompiledSlotRendering = (n: number) =>
+  (isRenderingCompiledSlot += n)
 
 /**
  * Compiler runtime helper for rendering `<slot/>`
index 4ac273f50b0949eaffea3dec5dd297cfef4d7b09..88a29ae32f1c418b09a432f4b1f124667ad7df84 100644 (file)
@@ -16,7 +16,7 @@ export function withCtx(
   ctx: ComponentInternalInstance | null = currentRenderingInstance
 ) {
   if (!ctx) return fn
-  return function renderFnWithContext() {
+  const renderFnWithContext = (...args: any[]) => {
     // If a user calls a compiled slot inside a template expression (#1745), it
     // can mess up block tracking, so by default we need to push a null block to
     // avoid that. This isn't necessary if rendering a compiled `<slot>`.
@@ -25,11 +25,13 @@ export function withCtx(
     }
     const owner = currentRenderingInstance
     setCurrentRenderingInstance(ctx)
-    const res = fn.apply(null, arguments as any)
+    const res = fn(...args)
     setCurrentRenderingInstance(owner)
     if (!isRenderingCompiledSlot) {
       closeBlock()
     }
     return res
   }
+  renderFnWithContext._c = true
+  return renderFnWithContext
 }
index 0f20a534675d5a9a6fec180d6ccb7af4eb05ea06..de329ff945518d7d2efcc9c59905289f78aed1f9 100644 (file)
@@ -36,6 +36,7 @@ import { currentRenderingInstance } from './componentRenderUtils'
 import { RendererNode, RendererElement } from './renderer'
 import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
 import { hmrDirtyComponents } from './hmr'
+import { setCompiledSlotRendering } from './helpers/renderSlot'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -539,12 +540,15 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
   } else if (isArray(children)) {
     type = ShapeFlags.ARRAY_CHILDREN
   } else if (typeof children === 'object') {
-    // Normalize slot to plain children
-    if (
-      (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) &&
-      (children as any).default
-    ) {
-      normalizeChildren(vnode, (children as any).default())
+    if (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) {
+      // Normalize slot to plain children for plain element and Teleport
+      const slot = (children as any).default
+      if (slot) {
+        // _c marker is added by withCtx() indicating this is a compiled slot
+        slot._c && setCompiledSlotRendering(1)
+        normalizeChildren(vnode, slot())
+        slot._c && setCompiledSlotRendering(-1)
+      }
       return
     } else {
       type = ShapeFlags.SLOTS_CHILDREN