fix(runtime-core): avoid manual slot invocation in template expressions interfering with block tracking
fix #1745
This commit is contained in:
parent
233d191d0d
commit
791eff3dfb
@ -1,5 +1,13 @@
|
|||||||
import { renderSlot } from '../../src/helpers/renderSlot'
|
import { renderSlot } from '../../src/helpers/renderSlot'
|
||||||
import { h } from '../../src/h'
|
import {
|
||||||
|
h,
|
||||||
|
withCtx,
|
||||||
|
createVNode,
|
||||||
|
openBlock,
|
||||||
|
createBlock,
|
||||||
|
Fragment
|
||||||
|
} from '../../src'
|
||||||
|
import { PatchFlags } from '@vue/shared/src'
|
||||||
|
|
||||||
describe('renderSlot', () => {
|
describe('renderSlot', () => {
|
||||||
it('should render slot', () => {
|
it('should render slot', () => {
|
||||||
@ -20,4 +28,23 @@ describe('renderSlot', () => {
|
|||||||
renderSlot({ default: (_a, _b, _c) => [h('child')] }, 'default')
|
renderSlot({ default: (_a, _b, _c) => [h('child')] }, 'default')
|
||||||
expect('SSR-optimized slot function detected').toHaveBeenWarned()
|
expect('SSR-optimized slot function detected').toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #1745
|
||||||
|
it('should force enable tracking', () => {
|
||||||
|
const slot = withCtx(
|
||||||
|
() => {
|
||||||
|
return [createVNode('div', null, 'foo', PatchFlags.TEXT)]
|
||||||
|
},
|
||||||
|
// mock instance
|
||||||
|
{} as any
|
||||||
|
)
|
||||||
|
|
||||||
|
// manual invocation should not track
|
||||||
|
const manual = (openBlock(), createBlock(Fragment, null, slot()))
|
||||||
|
expect(manual.dynamicChildren!.length).toBe(0)
|
||||||
|
|
||||||
|
// renderSlot should track
|
||||||
|
const templateRendered = renderSlot({ default: slot }, 'default')
|
||||||
|
expect(templateRendered.dynamicChildren!.length).toBe(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,6 +10,8 @@ import {
|
|||||||
import { PatchFlags, SlotFlags } from '@vue/shared'
|
import { PatchFlags, SlotFlags } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
|
export let isRenderingTemplateSlot = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiler runtime helper for rendering `<slot/>`
|
* Compiler runtime helper for rendering `<slot/>`
|
||||||
* @private
|
* @private
|
||||||
@ -33,15 +35,20 @@ export function renderSlot(
|
|||||||
slot = () => []
|
slot = () => []
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// a compiled slot disables block tracking by default to avoid manual
|
||||||
openBlock(),
|
// invocation interfering with template-based block tracking, but in
|
||||||
createBlock(
|
// `renderSlot` we can be sure that it's template-based so we can force
|
||||||
Fragment,
|
// enable it.
|
||||||
{ key: props.key },
|
isRenderingTemplateSlot = true
|
||||||
slot ? slot(props) : fallback ? fallback() : [],
|
const rendered = (openBlock(),
|
||||||
(slots as RawSlots)._ === SlotFlags.STABLE
|
createBlock(
|
||||||
? PatchFlags.STABLE_FRAGMENT
|
Fragment,
|
||||||
: PatchFlags.BAIL
|
{ key: props.key },
|
||||||
)
|
slot ? slot(props) : fallback ? fallback() : [],
|
||||||
)
|
(slots as RawSlots)._ === SlotFlags.STABLE
|
||||||
|
? PatchFlags.STABLE_FRAGMENT
|
||||||
|
: PatchFlags.BAIL
|
||||||
|
))
|
||||||
|
isRenderingTemplateSlot = false
|
||||||
|
return rendered
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
currentRenderingInstance
|
currentRenderingInstance
|
||||||
} from '../componentRenderUtils'
|
} from '../componentRenderUtils'
|
||||||
import { ComponentInternalInstance } from '../component'
|
import { ComponentInternalInstance } from '../component'
|
||||||
|
import { setBlockTracking } from '../vnode'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a slot function to memoize current rendering instance
|
* Wrap a slot function to memoize current rendering instance
|
||||||
@ -15,10 +16,15 @@ export function withCtx(
|
|||||||
) {
|
) {
|
||||||
if (!ctx) return fn
|
if (!ctx) return fn
|
||||||
return function renderFnWithContext() {
|
return function renderFnWithContext() {
|
||||||
|
// By default, compiled slots disables block tracking since the user may
|
||||||
|
// call it inside a template expression (#1745). It should only track when
|
||||||
|
// it's called by a template `<slot>`.
|
||||||
|
setBlockTracking(-1)
|
||||||
const owner = currentRenderingInstance
|
const owner = currentRenderingInstance
|
||||||
setCurrentRenderingInstance(ctx)
|
setCurrentRenderingInstance(ctx)
|
||||||
const res = fn.apply(null, arguments as any)
|
const res = fn.apply(null, arguments as any)
|
||||||
setCurrentRenderingInstance(owner)
|
setCurrentRenderingInstance(owner)
|
||||||
|
setBlockTracking(1)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import { currentRenderingInstance } from './componentRenderUtils'
|
|||||||
import { RendererNode, RendererElement } from './renderer'
|
import { RendererNode, RendererElement } from './renderer'
|
||||||
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
|
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
|
||||||
import { hmrDirtyComponents } from './hmr'
|
import { hmrDirtyComponents } from './hmr'
|
||||||
|
import { isRenderingTemplateSlot } from './helpers/renderSlot'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -400,18 +401,20 @@ function _createVNode(
|
|||||||
|
|
||||||
normalizeChildren(vnode, children)
|
normalizeChildren(vnode, children)
|
||||||
|
|
||||||
// presence of a patch flag indicates this node needs patching on updates.
|
|
||||||
// component nodes also should always be patched, because even if the
|
|
||||||
// component doesn't need to update, it needs to persist the instance on to
|
|
||||||
// the next vnode so that it can be properly unmounted later.
|
|
||||||
if (
|
if (
|
||||||
shouldTrack > 0 &&
|
(shouldTrack > 0 || isRenderingTemplateSlot) &&
|
||||||
|
// avoid a block node from tracking itself
|
||||||
!isBlockNode &&
|
!isBlockNode &&
|
||||||
|
// has current parent block
|
||||||
currentBlock &&
|
currentBlock &&
|
||||||
|
// presence of a patch flag indicates this node needs patching on updates.
|
||||||
|
// component nodes also should always be patched, because even if the
|
||||||
|
// component doesn't need to update, it needs to persist the instance on to
|
||||||
|
// the next vnode so that it can be properly unmounted later.
|
||||||
|
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
|
||||||
// the EVENTS flag is only for hydration and if it is the only flag, the
|
// the EVENTS flag is only for hydration and if it is the only flag, the
|
||||||
// vnode should not be considered dynamic due to handler caching.
|
// vnode should not be considered dynamic due to handler caching.
|
||||||
patchFlag !== PatchFlags.HYDRATE_EVENTS &&
|
patchFlag !== PatchFlags.HYDRATE_EVENTS
|
||||||
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT)
|
|
||||||
) {
|
) {
|
||||||
currentBlock.push(vnode)
|
currentBlock.push(vnode)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user