diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 68e1edcbe3c..be6dbba4a42 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -10,7 +10,7 @@ import { import { PatchFlags, SlotFlags } from '@vue/shared' import { warn } from '../warning' -export let shouldTrackInSlotRendering = 0 +export let isRenderingCompiledSlot = 0 /** * Compiler runtime helper for rendering `` @@ -39,7 +39,7 @@ export function renderSlot( // invocation interfering with template-based block tracking, but in // `renderSlot` we can be sure that it's template-based so we can force // enable it. - shouldTrackInSlotRendering++ + isRenderingCompiledSlot++ const rendered = (openBlock(), createBlock( Fragment, @@ -49,6 +49,6 @@ export function renderSlot( ? PatchFlags.STABLE_FRAGMENT : PatchFlags.BAIL )) - shouldTrackInSlotRendering-- + isRenderingCompiledSlot-- return rendered } diff --git a/packages/runtime-core/src/helpers/withRenderContext.ts b/packages/runtime-core/src/helpers/withRenderContext.ts index bf1541fa11a..4ac273f50b0 100644 --- a/packages/runtime-core/src/helpers/withRenderContext.ts +++ b/packages/runtime-core/src/helpers/withRenderContext.ts @@ -4,7 +4,8 @@ import { currentRenderingInstance } from '../componentRenderUtils' import { ComponentInternalInstance } from '../component' -import { setBlockTracking } from '../vnode' +import { isRenderingCompiledSlot } from './renderSlot' +import { closeBlock, openBlock } from '../vnode' /** * Wrap a slot function to memoize current rendering instance @@ -16,15 +17,19 @@ export function withCtx( ) { if (!ctx) return fn 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 ``. - setBlockTracking(-1) + // 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 ``. + if (!isRenderingCompiledSlot) { + openBlock(true /* null block that disables tracking */) + } const owner = currentRenderingInstance setCurrentRenderingInstance(ctx) const res = fn.apply(null, arguments as any) setCurrentRenderingInstance(owner) - setBlockTracking(1) + if (!isRenderingCompiledSlot) { + closeBlock() + } return res } } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 8eadedca27f..efdd2b1114b 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -36,7 +36,6 @@ import { currentRenderingInstance } from './componentRenderUtils' import { RendererNode, RendererElement } from './renderer' import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets' import { hmrDirtyComponents } from './hmr' -import { shouldTrackInSlotRendering } from './helpers/renderSlot' export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true @@ -153,7 +152,7 @@ export interface VNode< // can divide a template into nested blocks, and within each block the node // structure would be stable. This allows us to skip most children diffing // and only worry about the dynamic nodes (indicated by patch flags). -const blockStack: (VNode[] | null)[] = [] +export const blockStack: (VNode[] | null)[] = [] let currentBlock: VNode[] | null = null /** @@ -176,6 +175,11 @@ export function openBlock(disableTracking = false) { blockStack.push((currentBlock = disableTracking ? null : [])) } +export function closeBlock() { + blockStack.pop() + currentBlock = blockStack[blockStack.length - 1] || null +} + // Whether we should be tracking dynamic child nodes inside a block. // Only tracks when this value is > 0 // We are not using a simple boolean because this value may need to be @@ -227,8 +231,7 @@ export function createBlock( // save current block children on the block vnode vnode.dynamicChildren = currentBlock || EMPTY_ARR // close block - blockStack.pop() - currentBlock = blockStack[blockStack.length - 1] || null + closeBlock() // a block is always going to be patched, so track it as a child of its // parent block if (currentBlock) { @@ -403,7 +406,7 @@ function _createVNode( normalizeChildren(vnode, children) if ( - (shouldTrack > 0 || shouldTrackInSlotRendering > 0) && + shouldTrack > 0 && // avoid a block node from tracking itself !isBlockNode && // has current parent block