fix(runtime-core): enable block tracking when normalizing plain element with slot children (#1987)
fix #1980
This commit is contained in:
parent
706b52aadd
commit
5b82c48c7b
@ -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>')
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -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/>`
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user