Skip to content

Commit

Permalink
fix(runtime-core): enable block tracking when normalizing plain eleme…
Browse files Browse the repository at this point in the history
…nt with slot children (#1987)

fix #1980
  • Loading branch information
HcySunYang authored Sep 1, 2020
1 parent 706b52a commit 5b82c48
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 12 deletions.
55 changes: 53 additions & 2 deletions packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>')
})
})
4 changes: 2 additions & 2 deletions packages/runtime-core/__tests__/vnode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
})

Expand Down
2 changes: 2 additions & 0 deletions packages/runtime-core/src/helpers/renderSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/>`
Expand Down
6 changes: 4 additions & 2 deletions packages/runtime-core/src/helpers/withRenderContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>`.
Expand All @@ -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
}
16 changes: 10 additions & 6 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5b82c48

Please sign in to comment.