+175
-34
lines changedFilter options
+175
-34
lines changed Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import {
2
2
h,
3
3
Fragment,
4
4
createVNode,
5
+
createCommentVNode,
5
6
openBlock,
6
7
createBlock,
7
8
render,
@@ -576,4 +577,119 @@ describe('renderer: optimized mode', () => {
576
577
await nextTick()
577
578
expect(inner(root)).toBe('<div>World</div>')
578
579
})
580
+
581
+
// #3548
582
+
test('should not track dynamic children when the user calls a compiled slot inside template expression', () => {
583
+
const Comp = {
584
+
setup(props: any, { slots }: SetupContext) {
585
+
return () => {
586
+
return (
587
+
openBlock(),
588
+
(block = createBlock('section', null, [
589
+
renderSlot(slots, 'default')
590
+
]))
591
+
)
592
+
}
593
+
}
594
+
}
595
+
596
+
let dynamicVNode: VNode
597
+
const Wrapper = {
598
+
setup(props: any, { slots }: SetupContext) {
599
+
return () => {
600
+
return (
601
+
openBlock(),
602
+
createBlock(Comp, null, {
603
+
default: withCtx(() => {
604
+
return [
605
+
(dynamicVNode = createVNode(
606
+
'div',
607
+
{
608
+
class: {
609
+
foo: !!slots.default!()
610
+
}
611
+
},
612
+
null,
613
+
PatchFlags.CLASS
614
+
))
615
+
]
616
+
}),
617
+
_: 1
618
+
})
619
+
)
620
+
}
621
+
}
622
+
}
623
+
const app = createApp({
624
+
render() {
625
+
return (
626
+
openBlock(),
627
+
createBlock(Wrapper, null, {
628
+
default: withCtx(() => {
629
+
return [createVNode({}) /* component */]
630
+
}),
631
+
_: 1
632
+
})
633
+
)
634
+
}
635
+
})
636
+
637
+
app.mount(root)
638
+
expect(inner(root)).toBe('<section><div class="foo"></div></section>')
639
+
/**
640
+
* Block Tree:
641
+
* - block(div)
642
+
* - block(Fragment): renderSlots()
643
+
* - dynamicVNode
644
+
*/
645
+
expect(block!.dynamicChildren!.length).toBe(1)
646
+
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
647
+
expect(block!.dynamicChildren![0].dynamicChildren![0]).toEqual(
648
+
dynamicVNode!
649
+
)
650
+
})
651
+
652
+
// 3569
653
+
test('should force bailout when the user manually calls the slot function', async () => {
654
+
const index = ref(0)
655
+
const Foo = {
656
+
setup(props: any, { slots }: SetupContext) {
657
+
return () => {
658
+
return slots.default!()[index.value]
659
+
}
660
+
}
661
+
}
662
+
663
+
const app = createApp({
664
+
setup() {
665
+
return () => {
666
+
return (
667
+
openBlock(),
668
+
createBlock(Foo, null, {
669
+
default: withCtx(() => [
670
+
true
671
+
? (openBlock(), createBlock('p', { key: 0 }, '1'))
672
+
: createCommentVNode('v-if', true),
673
+
true
674
+
? (openBlock(), createBlock('p', { key: 0 }, '2'))
675
+
: createCommentVNode('v-if', true)
676
+
]),
677
+
_: 1 /* STABLE */
678
+
})
679
+
)
680
+
}
681
+
}
682
+
})
683
+
684
+
app.mount(root)
685
+
expect(inner(root)).toBe('<p>1</p>')
686
+
687
+
index.value = 1
688
+
await nextTick()
689
+
expect(inner(root)).toBe('<p>2</p>')
690
+
691
+
index.value = 0
692
+
await nextTick()
693
+
expect(inner(root)).toBe('<p>1</p>')
694
+
})
579
695
})
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ import {
37
37
import { resolveFilter } from '../helpers/resolveAssets'
38
38
import { resolveMergedOptions } from '../componentOptions'
39
39
import { InternalSlots, Slots } from '../componentSlots'
40
+
import { ContextualRenderFn } from '../componentRenderContext'
40
41
41
42
export type LegacyPublicInstance = ComponentPublicInstance &
42
43
LegacyPublicProperties
@@ -106,7 +107,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
106
107
const res: InternalSlots = {}
107
108
for (const key in i.slots) {
108
109
const fn = i.slots[key]!
109
-
if (!(fn as any)._nonScoped) {
110
+
if (!(fn as ContextualRenderFn)._ns /* non-scoped slot */) {
110
111
res[key] = fn
111
112
}
112
113
}
Original file line number Diff line number Diff line change
@@ -281,7 +281,7 @@ function convertLegacySlots(vnode: VNode): VNode {
281
281
for (const key in slots) {
282
282
const slotChildren = slots[key]
283
283
slots[key] = () => slotChildren
284
-
slots[key]._nonScoped = true
284
+
slots[key]._ns = true /* non-scoped slot */
285
285
}
286
286
}
287
287
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
1
1
import { ComponentInternalInstance } from './component'
2
2
import { devtoolsComponentUpdated } from './devtools'
3
-
import { isRenderingCompiledSlot } from './helpers/renderSlot'
4
-
import { closeBlock, openBlock } from './vnode'
3
+
import { setBlockTracking } from './vnode'
5
4
6
5
/**
7
6
* mark the current rendering instance for asset resolution (e.g.
@@ -56,6 +55,14 @@ export function popScopeId() {
56
55
*/
57
56
export const withScopeId = (_id: string) => withCtx
58
57
58
+
export type ContextualRenderFn = {
59
+
(...args: any[]): any
60
+
_n: boolean /* already normalized */
61
+
_c: boolean /* compiled */
62
+
_d: boolean /* disableTracking */
63
+
_ns: boolean /* nonScoped */
64
+
}
65
+
59
66
/**
60
67
* Wrap a slot function to memoize current rendering instance
61
68
* @private compiler helper
@@ -66,18 +73,26 @@ export function withCtx(
66
73
isNonScopedSlot?: boolean // __COMPAT__ only
67
74
) {
68
75
if (!ctx) return fn
69
-
const renderFnWithContext = (...args: any[]) => {
76
+
77
+
// already normalized
78
+
if ((fn as ContextualRenderFn)._n) {
79
+
return fn
80
+
}
81
+
82
+
const renderFnWithContext: ContextualRenderFn = (...args: any[]) => {
70
83
// If a user calls a compiled slot inside a template expression (#1745), it
71
-
// can mess up block tracking, so by default we need to push a null block to
72
-
// avoid that. This isn't necessary if rendering a compiled `<slot>`.
73
-
if (!isRenderingCompiledSlot) {
74
-
openBlock(true /* null block that disables tracking */)
84
+
// can mess up block tracking, so by default we disable block tracking and
85
+
// force bail out when invoking a compiled slot (indicated by the ._d flag).
86
+
// This isn't necessary if rendering a compiled `<slot>`, so we flip the
87
+
// ._d flag off when invoking the wrapped fn inside `renderSlot`.
88
+
if (renderFnWithContext._d) {
89
+
setBlockTracking(-1)
75
90
}
76
91
const prevInstance = setCurrentRenderingInstance(ctx)
77
92
const res = fn(...args)
78
93
setCurrentRenderingInstance(prevInstance)
79
-
if (!isRenderingCompiledSlot) {
80
-
closeBlock()
94
+
if (renderFnWithContext._d) {
95
+
setBlockTracking(1)
81
96
}
82
97
83
98
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
@@ -86,13 +101,18 @@ export function withCtx(
86
101
87
102
return res
88
103
}
89
-
// mark this as a compiled slot function.
104
+
105
+
// mark normalized to avoid duplicated wrapping
106
+
renderFnWithContext._n = true
107
+
// mark this as compiled by default
90
108
// this is used in vnode.ts -> normalizeChildren() to set the slot
91
109
// rendering flag.
92
-
// also used to cache the normalized results to avoid repeated normalization
93
-
renderFnWithContext._c = renderFnWithContext
110
+
renderFnWithContext._c = true
111
+
// disable block tracking by default
112
+
renderFnWithContext._d = true
113
+
// compat build only flag to distinguish scoped slots from non-scoped ones
94
114
if (__COMPAT__ && isNonScopedSlot) {
95
-
renderFnWithContext._nonScoped = true
115
+
renderFnWithContext._ns = true
96
116
}
97
117
return renderFnWithContext
98
118
}
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ import {
17
17
} from '@vue/shared'
18
18
import { warn } from './warning'
19
19
import { isKeepAlive } from './components/KeepAlive'
20
-
import { withCtx } from './componentRenderContext'
20
+
import { ContextualRenderFn, withCtx } from './componentRenderContext'
21
21
import { isHmrUpdating } from './hmr'
22
22
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
23
23
import { toRaw } from '@vue/reactivity'
@@ -62,9 +62,8 @@ const normalizeSlot = (
62
62
key: string,
63
63
rawSlot: Function,
64
64
ctx: ComponentInternalInstance | null | undefined
65
-
): Slot =>
66
-
(rawSlot as any)._c ||
67
-
(withCtx((props: any) => {
65
+
): Slot => {
66
+
const normalized = withCtx((props: any) => {
68
67
if (__DEV__ && currentInstance) {
69
68
warn(
70
69
`Slot "${key}" invoked outside of the render function: ` +
@@ -73,7 +72,11 @@ const normalizeSlot = (
73
72
)
74
73
}
75
74
return normalizeSlotValue(rawSlot(props))
76
-
}, ctx) as Slot)
75
+
}, ctx) as Slot
76
+
// NOT a compiled slot
77
+
;(normalized as ContextualRenderFn)._c = false
78
+
return normalized
79
+
}
77
80
78
81
const normalizeObjectSlots = (
79
82
rawSlots: RawSlots,
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
1
1
import { Data } from '../component'
2
2
import { Slots, RawSlots } from '../componentSlots'
3
+
import { ContextualRenderFn } from '../componentRenderContext'
3
4
import { Comment, isVNode } from '../vnode'
4
5
import {
5
6
VNodeArrayChildren,
@@ -11,10 +12,6 @@ import {
11
12
import { PatchFlags, SlotFlags } from '@vue/shared'
12
13
import { warn } from '../warning'
13
14
14
-
export let isRenderingCompiledSlot = 0
15
-
export const setCompiledSlotRendering = (n: number) =>
16
-
(isRenderingCompiledSlot += n)
17
-
18
15
/**
19
16
* Compiler runtime helper for rendering `<slot/>`
20
17
* @private
@@ -43,7 +40,9 @@ export function renderSlot(
43
40
// invocation interfering with template-based block tracking, but in
44
41
// `renderSlot` we can be sure that it's template-based so we can force
45
42
// enable it.
46
-
isRenderingCompiledSlot++
43
+
if (slot && (slot as ContextualRenderFn)._c) {
44
+
;(slot as ContextualRenderFn)._d = false
45
+
}
47
46
openBlock()
48
47
const validSlotContent = slot && ensureValidVNode(slot(props))
49
48
const rendered = createBlock(
@@ -57,7 +56,9 @@ export function renderSlot(
57
56
if (!noSlotted && rendered.scopeId) {
58
57
rendered.slotScopeIds = [rendered.scopeId + '-s']
59
58
}
60
-
isRenderingCompiledSlot--
59
+
if (slot && (slot as ContextualRenderFn)._c) {
60
+
;(slot as ContextualRenderFn)._d = true
61
+
}
61
62
return rendered
62
63
}
63
64
Original file line number Diff line number Diff line change
@@ -40,7 +40,6 @@ import {
40
40
import { RendererNode, RendererElement } from './renderer'
41
41
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
42
42
import { hmrDirtyComponents } from './hmr'
43
-
import { setCompiledSlotRendering } from './helpers/renderSlot'
44
43
import { convertLegacyComponent } from './compat/component'
45
44
import { convertLegacyVModelProps } from './compat/componentVModel'
46
45
import { defineLegacyVNodeProperties } from './compat/renderFn'
@@ -218,7 +217,7 @@ export function closeBlock() {
218
217
// Only tracks when this value is > 0
219
218
// We are not using a simple boolean because this value may need to be
220
219
// incremented/decremented by nested usage of v-once (see below)
221
-
let shouldTrack = 1
220
+
let isBlockTreeEnabled = 1
222
221
223
222
/**
224
223
* Block tracking sometimes needs to be disabled, for example during the
@@ -237,7 +236,7 @@ let shouldTrack = 1
237
236
* @private
238
237
*/
239
238
export function setBlockTracking(value: number) {
240
-
shouldTrack += value
239
+
isBlockTreeEnabled += value
241
240
}
242
241
243
242
/**
@@ -263,12 +262,13 @@ export function createBlock(
263
262
true /* isBlock: prevent a block from tracking itself */
264
263
)
265
264
// save current block children on the block vnode
266
-
vnode.dynamicChildren = currentBlock || (EMPTY_ARR as any)
265
+
vnode.dynamicChildren =
266
+
isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
267
267
// close block
268
268
closeBlock()
269
269
// a block is always going to be patched, so track it as a child of its
270
270
// parent block
271
-
if (shouldTrack > 0 && currentBlock) {
271
+
if (isBlockTreeEnabled > 0 && currentBlock) {
272
272
currentBlock.push(vnode)
273
273
}
274
274
return vnode
@@ -458,7 +458,7 @@ function _createVNode(
458
458
}
459
459
460
460
if (
461
-
shouldTrack > 0 &&
461
+
isBlockTreeEnabled > 0 &&
462
462
// avoid a block node from tracking itself
463
463
!isBlockNode &&
464
464
// has current parent block
@@ -635,9 +635,9 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
635
635
const slot = (children as any).default
636
636
if (slot) {
637
637
// _c marker is added by withCtx() indicating this is a compiled slot
638
-
slot._c && setCompiledSlotRendering(1)
638
+
slot._c && (slot._d = false)
639
639
normalizeChildren(vnode, slot())
640
-
slot._c && setCompiledSlotRendering(-1)
640
+
slot._c && (slot._d = true)
641
641
}
642
642
return
643
643
} else {
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4