mirror of https://github.com/vuejs/core.git
fix(compiler-core): handle template ref bound via v-bind object on v-for (#10706)
close #10696
This commit is contained in:
parent
5cef52a5c2
commit
da7adefa84
|
@ -0,0 +1,228 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > basic v-for 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
|
||||||
|
"hello",
|
||||||
|
_createElementVNode("span")
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}), 128 /* KEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > keyed v-for 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span", { key: item }))
|
||||||
|
}), 128 /* KEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > skipped key 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > skipped value & key 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > skipped value 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > template v-for 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
|
"hello",
|
||||||
|
_createElementVNode("span")
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span", {
|
||||||
|
key: item.id,
|
||||||
|
id: item.id
|
||||||
|
}, null, 8 /* PROPS */, ["id"]))
|
||||||
|
}), 128 /* KEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > template v-for w/ <slot/> 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return _renderSlot($slots, "default")
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > v-for on <slot/> 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
|
return _renderSlot($slots, "default")
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
|
||||||
|
|
||||||
|
const _directive_foo = _resolveDirective("foo")
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
|
return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
|
||||||
|
[_directive_foo]
|
||||||
|
])
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
|
||||||
|
return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
|
||||||
|
}), 64 /* STABLE_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
|
return ok
|
||||||
|
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
||||||
|
return (_openBlock(), _createElementBlock("div"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
: _createCommentVNode("v-if", true)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > v-if + v-for on <template> 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
|
return ok
|
||||||
|
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
||||||
|
return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
: _createCommentVNode("v-if", true)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > codegen > value + key + index 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
|
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
|
||||||
|
return (_openBlock(), _createElementBlock("span"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
|
@ -39,6 +39,7 @@ import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||||
import { transformText } from '../../src/transforms/transformText'
|
import { transformText } from '../../src/transforms/transformText'
|
||||||
|
import { parseWithForTransform } from './vFor.spec'
|
||||||
|
|
||||||
function parseWithElementTransform(
|
function parseWithElementTransform(
|
||||||
template: string,
|
template: string,
|
||||||
|
@ -1338,4 +1339,42 @@ describe('compiler: element transform', () => {
|
||||||
isBlock: false,
|
isBlock: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ref_for marker on static ref', () => {
|
||||||
|
const { node } = parseWithForTransform(`<div v-for="i in l" ref="x"/>`)
|
||||||
|
expect((node.children[0] as any).codegenNode.props).toMatchObject(
|
||||||
|
createObjectMatcher({
|
||||||
|
ref_for: `[true]`,
|
||||||
|
ref: 'x',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ref_for marker on dynamic ref', () => {
|
||||||
|
const { node } = parseWithForTransform(`<div v-for="i in l" :ref="x"/>`)
|
||||||
|
expect((node.children[0] as any).codegenNode.props).toMatchObject(
|
||||||
|
createObjectMatcher({
|
||||||
|
ref_for: `[true]`,
|
||||||
|
ref: '[x]',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ref_for marker on v-bind', () => {
|
||||||
|
const { node } = parseWithForTransform(`<div v-for="i in l" v-bind="x" />`)
|
||||||
|
expect((node.children[0] as any).codegenNode.props).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: MERGE_PROPS,
|
||||||
|
arguments: [
|
||||||
|
createObjectMatcher({
|
||||||
|
ref_for: `[true]`,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'x',
|
||||||
|
isStatic: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||||
|
|
||||||
function parseWithForTransform(
|
export function parseWithForTransform(
|
||||||
template: string,
|
template: string,
|
||||||
options: CompilerOptions = {},
|
options: CompilerOptions = {},
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -433,6 +433,18 @@ export function buildProps(
|
||||||
if (arg) mergeArgs.push(arg)
|
if (arg) mergeArgs.push(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark template ref on v-for
|
||||||
|
const pushRefVForMarker = () => {
|
||||||
|
if (context.scopes.vFor > 0) {
|
||||||
|
properties.push(
|
||||||
|
createObjectProperty(
|
||||||
|
createSimpleExpression('ref_for', true),
|
||||||
|
createSimpleExpression('true'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||||
if (isStaticExp(key)) {
|
if (isStaticExp(key)) {
|
||||||
const name = key.content
|
const name = key.content
|
||||||
|
@ -502,14 +514,7 @@ export function buildProps(
|
||||||
let isStatic = true
|
let isStatic = true
|
||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
if (context.scopes.vFor > 0) {
|
pushRefVForMarker()
|
||||||
properties.push(
|
|
||||||
createObjectProperty(
|
|
||||||
createSimpleExpression('ref_for', true),
|
|
||||||
createSimpleExpression('true'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// in inline mode there is no setupState object, so we can't use string
|
// in inline mode there is no setupState object, so we can't use string
|
||||||
// keys to set the ref. Instead, we need to transform it to pass the
|
// keys to set the ref. Instead, we need to transform it to pass the
|
||||||
// actual ref instead.
|
// actual ref instead.
|
||||||
|
@ -601,13 +606,8 @@ export function buildProps(
|
||||||
shouldUseBlock = true
|
shouldUseBlock = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
|
if (isVBind && isStaticArgOf(arg, 'ref')) {
|
||||||
properties.push(
|
pushRefVForMarker()
|
||||||
createObjectProperty(
|
|
||||||
createSimpleExpression('ref_for', true),
|
|
||||||
createSimpleExpression('true'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
|
@ -615,6 +615,8 @@ export function buildProps(
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (isVBind) {
|
if (isVBind) {
|
||||||
|
// #10696 in case a v-bind object contains ref
|
||||||
|
pushRefVForMarker()
|
||||||
// have to merge early for compat build check
|
// have to merge early for compat build check
|
||||||
pushMergeArg()
|
pushMergeArg()
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
|
|
Loading…
Reference in New Issue