Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime-core): dynamicChildren should be tracked correctly when normalizing slots to plain children #1987

Merged
merged 3 commits into from
Sep 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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