Skip to content

Commit da7adef

Browse files
authoredApr 22, 2024··
fix(compiler-core): handle template ref bound via v-bind object on v-for (#10706)
close #10696
1 parent 5cef52a commit da7adef

File tree

4 files changed

+285
-16
lines changed

4 files changed

+285
-16
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`compiler: v-for > codegen > basic v-for 1`] = `
4+
"const _Vue = Vue
5+
6+
return function render(_ctx, _cache) {
7+
with (_ctx) {
8+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
9+
10+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
11+
return (_openBlock(), _createElementBlock("span"))
12+
}), 256 /* UNKEYED_FRAGMENT */))
13+
}
14+
}"
15+
`;
16+
17+
exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
18+
"const _Vue = Vue
19+
20+
return function render(_ctx, _cache) {
21+
with (_ctx) {
22+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
23+
24+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
25+
return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
26+
"hello",
27+
_createElementVNode("span")
28+
], 64 /* STABLE_FRAGMENT */))
29+
}), 128 /* KEYED_FRAGMENT */))
30+
}
31+
}"
32+
`;
33+
34+
exports[`compiler: v-for > codegen > keyed v-for 1`] = `
35+
"const _Vue = Vue
36+
37+
return function render(_ctx, _cache) {
38+
with (_ctx) {
39+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
40+
41+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
42+
return (_openBlock(), _createElementBlock("span", { key: item }))
43+
}), 128 /* KEYED_FRAGMENT */))
44+
}
45+
}"
46+
`;
47+
48+
exports[`compiler: v-for > codegen > skipped key 1`] = `
49+
"const _Vue = Vue
50+
51+
return function render(_ctx, _cache) {
52+
with (_ctx) {
53+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
54+
55+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
56+
return (_openBlock(), _createElementBlock("span"))
57+
}), 256 /* UNKEYED_FRAGMENT */))
58+
}
59+
}"
60+
`;
61+
62+
exports[`compiler: v-for > codegen > skipped value & key 1`] = `
63+
"const _Vue = Vue
64+
65+
return function render(_ctx, _cache) {
66+
with (_ctx) {
67+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
68+
69+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
70+
return (_openBlock(), _createElementBlock("span"))
71+
}), 256 /* UNKEYED_FRAGMENT */))
72+
}
73+
}"
74+
`;
75+
76+
exports[`compiler: v-for > codegen > skipped value 1`] = `
77+
"const _Vue = Vue
78+
79+
return function render(_ctx, _cache) {
80+
with (_ctx) {
81+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
82+
83+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
84+
return (_openBlock(), _createElementBlock("span"))
85+
}), 256 /* UNKEYED_FRAGMENT */))
86+
}
87+
}"
88+
`;
89+
90+
exports[`compiler: v-for > codegen > template v-for 1`] = `
91+
"const _Vue = Vue
92+
93+
return function render(_ctx, _cache) {
94+
with (_ctx) {
95+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
96+
97+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
98+
return (_openBlock(), _createElementBlock(_Fragment, null, [
99+
"hello",
100+
_createElementVNode("span")
101+
], 64 /* STABLE_FRAGMENT */))
102+
}), 256 /* UNKEYED_FRAGMENT */))
103+
}
104+
}"
105+
`;
106+
107+
exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
108+
"const _Vue = Vue
109+
110+
return function render(_ctx, _cache) {
111+
with (_ctx) {
112+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
113+
114+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
115+
return (_openBlock(), _createElementBlock("span", {
116+
key: item.id,
117+
id: item.id
118+
}, null, 8 /* PROPS */, ["id"]))
119+
}), 128 /* KEYED_FRAGMENT */))
120+
}
121+
}"
122+
`;
123+
124+
exports[`compiler: v-for > codegen > template v-for w/ <slot/> 1`] = `
125+
"const _Vue = Vue
126+
127+
return function render(_ctx, _cache) {
128+
with (_ctx) {
129+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
130+
131+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
132+
return _renderSlot($slots, "default")
133+
}), 256 /* UNKEYED_FRAGMENT */))
134+
}
135+
}"
136+
`;
137+
138+
exports[`compiler: v-for > codegen > v-for on <slot/> 1`] = `
139+
"const _Vue = Vue
140+
141+
return function render(_ctx, _cache) {
142+
with (_ctx) {
143+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
144+
145+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
146+
return _renderSlot($slots, "default")
147+
}), 256 /* UNKEYED_FRAGMENT */))
148+
}
149+
}"
150+
`;
151+
152+
exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
153+
"const _Vue = Vue
154+
155+
return function render(_ctx, _cache) {
156+
with (_ctx) {
157+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
158+
159+
const _directive_foo = _resolveDirective("foo")
160+
161+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
162+
return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
163+
[_directive_foo]
164+
])
165+
}), 256 /* UNKEYED_FRAGMENT */))
166+
}
167+
}"
168+
`;
169+
170+
exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
171+
"const _Vue = Vue
172+
173+
return function render(_ctx, _cache) {
174+
with (_ctx) {
175+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
176+
177+
return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
178+
return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
179+
}), 64 /* STABLE_FRAGMENT */))
180+
}
181+
}"
182+
`;
183+
184+
exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
185+
"const _Vue = Vue
186+
187+
return function render(_ctx, _cache) {
188+
with (_ctx) {
189+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
190+
191+
return ok
192+
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
193+
return (_openBlock(), _createElementBlock("div"))
194+
}), 256 /* UNKEYED_FRAGMENT */))
195+
: _createCommentVNode("v-if", true)
196+
}
197+
}"
198+
`;
199+
200+
exports[`compiler: v-for > codegen > v-if + v-for on <template> 1`] = `
201+
"const _Vue = Vue
202+
203+
return function render(_ctx, _cache) {
204+
with (_ctx) {
205+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
206+
207+
return ok
208+
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
209+
return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
210+
}), 256 /* UNKEYED_FRAGMENT */))
211+
: _createCommentVNode("v-if", true)
212+
}
213+
}"
214+
`;
215+
216+
exports[`compiler: v-for > codegen > value + key + index 1`] = `
217+
"const _Vue = Vue
218+
219+
return function render(_ctx, _cache) {
220+
with (_ctx) {
221+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
222+
223+
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
224+
return (_openBlock(), _createElementBlock("span"))
225+
}), 256 /* UNKEYED_FRAGMENT */))
226+
}
227+
}"
228+
`;

‎packages/compiler-core/__tests__/transforms/transformElement.spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { transformBind } from '../../src/transforms/vBind'
3939
import { PatchFlags } from '@vue/shared'
4040
import { createObjectMatcher, genFlagText } from '../testUtils'
4141
import { transformText } from '../../src/transforms/transformText'
42+
import { parseWithForTransform } from './vFor.spec'
4243

4344
function parseWithElementTransform(
4445
template: string,
@@ -1338,4 +1339,42 @@ describe('compiler: element transform', () => {
13381339
isBlock: false,
13391340
})
13401341
})
1342+
1343+
test('ref_for marker on static ref', () => {
1344+
const { node } = parseWithForTransform(`<div v-for="i in l" ref="x"/>`)
1345+
expect((node.children[0] as any).codegenNode.props).toMatchObject(
1346+
createObjectMatcher({
1347+
ref_for: `[true]`,
1348+
ref: 'x',
1349+
}),
1350+
)
1351+
})
1352+
1353+
test('ref_for marker on dynamic ref', () => {
1354+
const { node } = parseWithForTransform(`<div v-for="i in l" :ref="x"/>`)
1355+
expect((node.children[0] as any).codegenNode.props).toMatchObject(
1356+
createObjectMatcher({
1357+
ref_for: `[true]`,
1358+
ref: '[x]',
1359+
}),
1360+
)
1361+
})
1362+
1363+
test('ref_for marker on v-bind', () => {
1364+
const { node } = parseWithForTransform(`<div v-for="i in l" v-bind="x" />`)
1365+
expect((node.children[0] as any).codegenNode.props).toMatchObject({
1366+
type: NodeTypes.JS_CALL_EXPRESSION,
1367+
callee: MERGE_PROPS,
1368+
arguments: [
1369+
createObjectMatcher({
1370+
ref_for: `[true]`,
1371+
}),
1372+
{
1373+
type: NodeTypes.SIMPLE_EXPRESSION,
1374+
content: 'x',
1375+
isStatic: false,
1376+
},
1377+
],
1378+
})
1379+
})
13411380
})

‎packages/compiler-core/__tests__/transforms/vFor.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
2121
import { PatchFlags } from '@vue/shared'
2222
import { createObjectMatcher, genFlagText } from '../testUtils'
2323

24-
function parseWithForTransform(
24+
export function parseWithForTransform(
2525
template: string,
2626
options: CompilerOptions = {},
2727
) {

‎packages/compiler-core/src/transforms/transformElement.ts

+17-15
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,18 @@ export function buildProps(
433433
if (arg) mergeArgs.push(arg)
434434
}
435435

436+
// mark template ref on v-for
437+
const pushRefVForMarker = () => {
438+
if (context.scopes.vFor > 0) {
439+
properties.push(
440+
createObjectProperty(
441+
createSimpleExpression('ref_for', true),
442+
createSimpleExpression('true'),
443+
),
444+
)
445+
}
446+
}
447+
436448
const analyzePatchFlag = ({ key, value }: Property) => {
437449
if (isStaticExp(key)) {
438450
const name = key.content
@@ -502,14 +514,7 @@ export function buildProps(
502514
let isStatic = true
503515
if (name === 'ref') {
504516
hasRef = true
505-
if (context.scopes.vFor > 0) {
506-
properties.push(
507-
createObjectProperty(
508-
createSimpleExpression('ref_for', true),
509-
createSimpleExpression('true'),
510-
),
511-
)
512-
}
517+
pushRefVForMarker()
513518
// in inline mode there is no setupState object, so we can't use string
514519
// keys to set the ref. Instead, we need to transform it to pass the
515520
// actual ref instead.
@@ -601,20 +606,17 @@ export function buildProps(
601606
shouldUseBlock = true
602607
}
603608

604-
if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
605-
properties.push(
606-
createObjectProperty(
607-
createSimpleExpression('ref_for', true),
608-
createSimpleExpression('true'),
609-
),
610-
)
609+
if (isVBind && isStaticArgOf(arg, 'ref')) {
610+
pushRefVForMarker()
611611
}
612612

613613
// special case for v-bind and v-on with no argument
614614
if (!arg && (isVBind || isVOn)) {
615615
hasDynamicKeys = true
616616
if (exp) {
617617
if (isVBind) {
618+
// #10696 in case a v-bind object contains ref
619+
pushRefVForMarker()
618620
// have to merge early for compat build check
619621
pushMergeArg()
620622
if (__COMPAT__) {

0 commit comments

Comments
 (0)
Please sign in to comment.