fix(runtime-core): disable block tracking when calling compiled slot function in tempalte expressions

fix #1745, fix #1918
This commit is contained in:
Evan You 2020-08-21 12:47:45 -04:00
parent c0411b3745
commit f02e2f99d9
3 changed files with 22 additions and 14 deletions

View File

@ -10,7 +10,7 @@ import {
import { PatchFlags, SlotFlags } from '@vue/shared' import { PatchFlags, SlotFlags } from '@vue/shared'
import { warn } from '../warning' import { warn } from '../warning'
export let shouldTrackInSlotRendering = 0 export let isRenderingCompiledSlot = 0
/** /**
* Compiler runtime helper for rendering `<slot/>` * Compiler runtime helper for rendering `<slot/>`
@ -39,7 +39,7 @@ export function renderSlot(
// invocation interfering with template-based block tracking, but in // invocation interfering with template-based block tracking, but in
// `renderSlot` we can be sure that it's template-based so we can force // `renderSlot` we can be sure that it's template-based so we can force
// enable it. // enable it.
shouldTrackInSlotRendering++ isRenderingCompiledSlot++
const rendered = (openBlock(), const rendered = (openBlock(),
createBlock( createBlock(
Fragment, Fragment,
@ -49,6 +49,6 @@ export function renderSlot(
? PatchFlags.STABLE_FRAGMENT ? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL : PatchFlags.BAIL
)) ))
shouldTrackInSlotRendering-- isRenderingCompiledSlot--
return rendered return rendered
} }

View File

@ -4,7 +4,8 @@ import {
currentRenderingInstance currentRenderingInstance
} from '../componentRenderUtils' } from '../componentRenderUtils'
import { ComponentInternalInstance } from '../component' 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 * Wrap a slot function to memoize current rendering instance
@ -16,15 +17,19 @@ 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 // If a user calls a compiled slot inside a template expression (#1745), it
// call it inside a template expression (#1745). It should only track when // can mess up block tracking, so by default we need to push a null block to
// it's called by a template `<slot>`. // avoid that. This isn't necessary if rendering a compiled `<slot>`.
setBlockTracking(-1) if (!isRenderingCompiledSlot) {
openBlock(true /* null block that disables tracking */)
}
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) if (!isRenderingCompiledSlot) {
closeBlock()
}
return res return res
} }
} }

View File

@ -36,7 +36,6 @@ 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 { shouldTrackInSlotRendering } 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
@ -153,7 +152,7 @@ export interface VNode<
// can divide a template into nested blocks, and within each block the node // 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 // structure would be stable. This allows us to skip most children diffing
// and only worry about the dynamic nodes (indicated by patch flags). // 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 let currentBlock: VNode[] | null = null
/** /**
@ -176,6 +175,11 @@ export function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : [])) 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. // Whether we should be tracking dynamic child nodes inside a block.
// Only tracks when this value is > 0 // Only tracks when this value is > 0
// We are not using a simple boolean because this value may need to be // 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 // save current block children on the block vnode
vnode.dynamicChildren = currentBlock || EMPTY_ARR vnode.dynamicChildren = currentBlock || EMPTY_ARR
// close block // close block
blockStack.pop() closeBlock()
currentBlock = blockStack[blockStack.length - 1] || null
// a block is always going to be patched, so track it as a child of its // a block is always going to be patched, so track it as a child of its
// parent block // parent block
if (currentBlock) { if (currentBlock) {
@ -403,7 +406,7 @@ function _createVNode(
normalizeChildren(vnode, children) normalizeChildren(vnode, children)
if ( if (
(shouldTrack > 0 || shouldTrackInSlotRendering > 0) && shouldTrack > 0 &&
// avoid a block node from tracking itself // avoid a block node from tracking itself
!isBlockNode && !isBlockNode &&
// has current parent block // has current parent block