Skip to content

Commit 41c18ef

Browse files
committed
feat: support ref in v-for, remove compat deprecation warnings
1 parent a1167c5 commit 41c18ef

File tree

11 files changed

+216
-326
lines changed

11 files changed

+216
-326
lines changed

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

+10-95
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ describe('compiler: element transform', () => {
936936
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
937937
})
938938

939-
test('the binding exists (inline ref input)', () => {
939+
test('script setup inline mode template ref (binding exists)', () => {
940940
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
941941
inline: true,
942942
bindingMetadata: {
@@ -949,130 +949,45 @@ describe('compiler: element transform', () => {
949949
{
950950
type: NodeTypes.JS_PROPERTY,
951951
key: {
952-
type: NodeTypes.SIMPLE_EXPRESSION,
953-
content: 'ref',
952+
content: 'ref_key',
954953
isStatic: true
955954
},
956955
value: {
957-
type: NodeTypes.JS_FUNCTION_EXPRESSION,
958-
params: ['_value', '_refs'],
959-
body: {
960-
type: NodeTypes.JS_BLOCK_STATEMENT,
961-
body: [
962-
{
963-
content: `_refs['input'] = _value`
964-
},
965-
{
966-
content: 'input.value = _value'
967-
}
968-
]
969-
}
970-
}
971-
}
972-
]
973-
})
974-
})
975-
976-
test('the binding not exists (inline ref input)', () => {
977-
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
978-
inline: true
979-
})
980-
expect(node.props).toMatchObject({
981-
type: NodeTypes.JS_OBJECT_EXPRESSION,
982-
properties: [
983-
{
984-
type: NodeTypes.JS_PROPERTY,
985-
key: {
986-
type: NodeTypes.SIMPLE_EXPRESSION,
987-
content: 'ref',
956+
content: 'input',
988957
isStatic: true
989-
},
990-
value: {
991-
type: NodeTypes.JS_FUNCTION_EXPRESSION,
992-
params: ['_value', '_refs'],
993-
body: {
994-
type: NodeTypes.JS_BLOCK_STATEMENT,
995-
body: [
996-
{
997-
content: `_refs['input'] = _value`
998-
}
999-
]
1000-
}
1001958
}
1002-
}
1003-
]
1004-
})
1005-
})
1006-
1007-
test('the binding not exists (inline maybe ref input)', () => {
1008-
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
1009-
inline: true,
1010-
bindingMetadata: {
1011-
input: BindingTypes.SETUP_MAYBE_REF
1012-
}
1013-
})
1014-
expect(node.props).toMatchObject({
1015-
type: NodeTypes.JS_OBJECT_EXPRESSION,
1016-
properties: [
959+
},
1017960
{
1018961
type: NodeTypes.JS_PROPERTY,
1019962
key: {
1020-
type: NodeTypes.SIMPLE_EXPRESSION,
1021963
content: 'ref',
1022964
isStatic: true
1023965
},
1024966
value: {
1025-
type: NodeTypes.JS_FUNCTION_EXPRESSION,
1026-
params: ['_value', '_refs'],
1027-
body: {
1028-
type: NodeTypes.JS_BLOCK_STATEMENT,
1029-
body: [
1030-
{
1031-
content: `_refs['input'] = _value`
1032-
},
1033-
{
1034-
content: '_isRef(input) && (input.value = _value)'
1035-
}
1036-
]
1037-
}
967+
content: 'input',
968+
isStatic: false
1038969
}
1039970
}
1040971
]
1041972
})
1042973
})
1043974

1044-
test('the binding not exists (inline let ref input)', () => {
975+
test('script setup inline mode template ref (binding does not exist)', () => {
1045976
const { node } = parseWithElementTransform(`<input ref="input"/>`, {
1046-
inline: true,
1047-
bindingMetadata: {
1048-
input: BindingTypes.SETUP_LET
1049-
}
977+
inline: true
1050978
})
1051979
expect(node.props).toMatchObject({
1052980
type: NodeTypes.JS_OBJECT_EXPRESSION,
1053981
properties: [
1054982
{
1055983
type: NodeTypes.JS_PROPERTY,
1056984
key: {
1057-
type: NodeTypes.SIMPLE_EXPRESSION,
1058985
content: 'ref',
1059986
isStatic: true
1060987
},
1061988
value: {
1062-
type: NodeTypes.JS_FUNCTION_EXPRESSION,
1063-
params: ['_value', '_refs'],
1064-
body: {
1065-
type: NodeTypes.JS_BLOCK_STATEMENT,
1066-
body: [
1067-
{
1068-
content: `_refs['input'] = _value`
1069-
},
1070-
{
1071-
content:
1072-
'_isRef(input) ? input.value = _value : input = _value'
1073-
}
1074-
]
1075-
}
989+
content: 'input',
990+
isStatic: true
1076991
}
1077992
}
1078993
]

packages/compiler-core/src/compat/compatConfig.ts

-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const enum CompilerDeprecationTypes {
2020
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
2121
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
2222
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
23-
COMPILER_V_FOR_REF = 'COMPILER_V_FOR_REF',
2423
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
2524
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
2625
COMPILER_FILTERS = 'COMPILER_FILTER'
@@ -79,13 +78,6 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
7978
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html`
8079
},
8180

82-
[CompilerDeprecationTypes.COMPILER_V_FOR_REF]: {
83-
message:
84-
`Ref usage on v-for no longer creates array ref values in Vue 3. ` +
85-
`Consider using function refs or refactor to avoid ref usage altogether.`,
86-
link: `https://v3.vuejs.org/guide/migration/array-refs.html`
87-
},
88-
8981
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
9082
message:
9183
`<template> with no special directives will render as a native template ` +

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

+37-62
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ import {
1919
TemplateTextChildNode,
2020
DirectiveArguments,
2121
createVNodeCall,
22-
ConstantTypes,
23-
JSChildNode,
24-
createFunctionExpression,
25-
createBlockStatement
22+
ConstantTypes
2623
} from '../ast'
2724
import {
2825
PatchFlags,
@@ -48,8 +45,7 @@ import {
4845
KEEP_ALIVE,
4946
SUSPENSE,
5047
UNREF,
51-
GUARD_REACTIVE_PROPS,
52-
IS_REF
48+
GUARD_REACTIVE_PROPS
5349
} from '../runtimeHelpers'
5450
import {
5551
getInnerRange,
@@ -467,20 +463,32 @@ export function buildProps(
467463
const prop = props[i]
468464
if (prop.type === NodeTypes.ATTRIBUTE) {
469465
const { loc, name, value } = prop
470-
let valueNode = createSimpleExpression(
471-
value ? value.content : '',
472-
true,
473-
value ? value.loc : loc
474-
) as JSChildNode
466+
let isStatic = true
475467
if (name === 'ref') {
476468
hasRef = true
469+
if (context.scopes.vFor > 0) {
470+
properties.push(
471+
createObjectProperty(
472+
createSimpleExpression('ref_for', true),
473+
createSimpleExpression('true')
474+
)
475+
)
476+
}
477477
// in inline mode there is no setupState object, so we can't use string
478478
// keys to set the ref. Instead, we need to transform it to pass the
479479
// actual ref instead.
480-
if (!__BROWSER__ && context.inline && value?.content) {
481-
valueNode = createFunctionExpression(['_value', '_refs'])
482-
valueNode.body = createBlockStatement(
483-
processInlineRef(context, value.content)
480+
if (
481+
!__BROWSER__ &&
482+
value &&
483+
context.inline &&
484+
context.bindingMetadata[value.content]
485+
) {
486+
isStatic = false
487+
properties.push(
488+
createObjectProperty(
489+
createSimpleExpression('ref_key', true),
490+
createSimpleExpression(value.content, true, value.loc)
491+
)
484492
)
485493
}
486494
}
@@ -504,7 +512,11 @@ export function buildProps(
504512
true,
505513
getInnerRange(loc, 0, name.length)
506514
),
507-
valueNode
515+
createSimpleExpression(
516+
value ? value.content : '',
517+
isStatic,
518+
value ? value.loc : loc
519+
)
508520
)
509521
)
510522
} else {
@@ -555,6 +567,15 @@ export function buildProps(
555567
shouldUseBlock = true
556568
}
557569

570+
if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
571+
properties.push(
572+
createObjectProperty(
573+
createSimpleExpression('ref_for', true),
574+
createSimpleExpression('true')
575+
)
576+
)
577+
}
578+
558579
// special case for v-bind and v-on with no argument
559580
if (!arg && (isVBind || isVOn)) {
560581
hasDynamicKeys = true
@@ -654,25 +675,6 @@ export function buildProps(
654675
}
655676
}
656677
}
657-
658-
if (
659-
__COMPAT__ &&
660-
prop.type === NodeTypes.ATTRIBUTE &&
661-
prop.name === 'ref' &&
662-
context.scopes.vFor > 0 &&
663-
checkCompatEnabled(
664-
CompilerDeprecationTypes.COMPILER_V_FOR_REF,
665-
context,
666-
prop.loc
667-
)
668-
) {
669-
properties.push(
670-
createObjectProperty(
671-
createSimpleExpression('refInFor', true),
672-
createSimpleExpression('true', false)
673-
)
674-
)
675-
}
676678
}
677679

678680
let propsExpression: PropsExpression | undefined = undefined
@@ -914,30 +916,3 @@ function stringifyDynamicPropNames(props: string[]): string {
914916
function isComponentTag(tag: string) {
915917
return tag === 'component' || tag === 'Component'
916918
}
917-
918-
function processInlineRef(
919-
context: TransformContext,
920-
raw: string
921-
): JSChildNode[] {
922-
const body = [createSimpleExpression(`_refs['${raw}'] = _value`)]
923-
const { bindingMetadata, helperString } = context
924-
const type = bindingMetadata[raw]
925-
if (type === BindingTypes.SETUP_REF) {
926-
body.push(createSimpleExpression(`${raw}.value = _value`))
927-
} else if (type === BindingTypes.SETUP_MAYBE_REF) {
928-
body.push(
929-
createSimpleExpression(
930-
`${helperString(IS_REF)}(${raw}) && (${raw}.value = _value)`
931-
)
932-
)
933-
} else if (type === BindingTypes.SETUP_LET) {
934-
body.push(
935-
createSimpleExpression(
936-
`${helperString(
937-
IS_REF
938-
)}(${raw}) ? ${raw}.value = _value : ${raw} = _value`
939-
)
940-
)
941-
}
942-
return body
943-
}

0 commit comments

Comments
 (0)