Skip to content

Commit 86703c2

Browse files
committed
wip: ref v-for compat
1 parent 3e815be commit 86703c2

File tree

7 files changed

+117
-28
lines changed

7 files changed

+117
-28
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ 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',
2324
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
2425
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
2526
COMPILER_FILTERS = 'COMPILER_FILTER'
@@ -78,6 +79,13 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
7879
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html`
7980
},
8081

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+
8189
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
8290
message:
8391
`<template> with no special directives will render as a native template ` +

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

+19
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,25 @@ export function buildProps(
590590
runtimeDirectives.push(prop)
591591
}
592592
}
593+
594+
if (
595+
__COMPAT__ &&
596+
prop.type === NodeTypes.ATTRIBUTE &&
597+
prop.name === 'ref' &&
598+
context.scopes.vFor > 0 &&
599+
checkCompatEnabled(
600+
CompilerDeprecationTypes.COMPILER_V_FOR_REF,
601+
context,
602+
prop.loc
603+
)
604+
) {
605+
properties.push(
606+
createObjectProperty(
607+
createSimpleExpression('refInFor', true),
608+
createSimpleExpression('true', false)
609+
)
610+
)
611+
}
593612
}
594613

595614
let propsExpression: PropsExpression | undefined = undefined

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const enum DeprecationTypes {
4444
WATCH_ARRAY = 'WATCH_ARRAY',
4545
PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS',
4646

47+
V_FOR_REF = 'V_FOR_REF',
4748
V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER',
4849
CUSTOM_DIR = 'CUSTOM_DIR',
4950

@@ -287,6 +288,13 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
287288
link: `https://v3.vuejs.org/guide/migration/custom-directives.html`
288289
},
289290

291+
[DeprecationTypes.V_FOR_REF]: {
292+
message:
293+
`Ref usage on v-for no longer creates array ref values in Vue 3. ` +
294+
`Consider using function refs or refactor to avoid ref usage altogether.`,
295+
link: `https://v3.vuejs.org/guide/migration/array-refs.html`
296+
},
297+
290298
[DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
291299
message:
292300
`Using keyCode as v-on modifier is no longer supported. ` +
@@ -447,16 +455,18 @@ export function warnDeprecation(
447455
}
448456

449457
const dupKey = key + args.join('')
450-
const compName = instance && formatComponentName(instance, instance.type)
458+
let compId: string | number | null =
459+
instance && formatComponentName(instance, instance.type)
460+
if (compId === 'Anonymous' && instance) {
461+
compId = instance.uid
462+
}
451463

452464
// skip if the same warning is emitted for the same component type
453-
if (compName !== `Anonymous`) {
454-
const componentDupKey = dupKey + compName
455-
if (componentDupKey in instanceWarned) {
456-
return
457-
}
458-
instanceWarned[componentDupKey] = true
465+
const componentDupKey = dupKey + compId
466+
if (componentDupKey in instanceWarned) {
467+
return
459468
}
469+
instanceWarned[componentDupKey] = true
460470

461471
// same warning, but different component. skip the long message and just
462472
// log the key and count.
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { isArray, remove } from '@vue/shared'
2+
import { ComponentInternalInstance, Data } from '../component'
3+
import { VNode } from '../vnode'
4+
import { DeprecationTypes, warnDeprecation } from './compatConfig'
5+
6+
export function convertLegacyRefInFor(vnode: VNode) {
7+
// refInFor
8+
if (vnode.props && vnode.props.refInFor) {
9+
delete vnode.props.refInFor
10+
if (vnode.ref) {
11+
if (isArray(vnode.ref)) {
12+
vnode.ref.forEach(r => (r.f = true))
13+
} else {
14+
vnode.ref.f = true
15+
}
16+
}
17+
}
18+
}
19+
20+
export function registerLegacyRef(
21+
refs: Data,
22+
key: string,
23+
value: any,
24+
owner: ComponentInternalInstance,
25+
isInFor: boolean | undefined,
26+
isUnmount: boolean
27+
) {
28+
const existing = refs[key]
29+
if (isUnmount) {
30+
if (isArray(existing)) {
31+
remove(existing, value)
32+
} else {
33+
refs[key] = null
34+
}
35+
} else if (isInFor) {
36+
__DEV__ && warnDeprecation(DeprecationTypes.V_FOR_REF, owner)
37+
if (!isArray(existing)) {
38+
refs[key] = [value]
39+
} else if (!existing.includes(value)) {
40+
existing.push(value)
41+
}
42+
} else {
43+
refs[key] = value
44+
}
45+
}

packages/runtime-core/src/compat/renderFn.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function compatH(
171171
}
172172

173173
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
174-
'refInFor,staticStyle,staticClass,directives,model'
174+
'staticStyle,staticClass,directives,model,hook'
175175
)
176176

177177
function convertLegacyProps(
@@ -206,8 +206,6 @@ function convertLegacyProps(
206206
}
207207
}
208208
}
209-
} else if (key === 'hook') {
210-
// TODO
211209
} else if (!skipLegacyRootLevelProps(key)) {
212210
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
213211
}

packages/runtime-core/src/renderer.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ import {
7676
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
7777
import { invokeDirectiveHook } from './directives'
7878
import { startMeasure, endMeasure } from './profiling'
79-
import { ComponentPublicInstance } from './componentPublicInstance'
8079
import {
8180
devtoolsComponentAdded,
8281
devtoolsComponentRemoved,
@@ -87,6 +86,7 @@ import { initFeatureFlags } from './featureFlags'
8786
import { isAsyncWrapper } from './apiAsyncComponent'
8887
import { isCompatEnabled } from './compat/compatConfig'
8988
import { DeprecationTypes } from './compat/compatConfig'
89+
import { registerLegacyRef } from './compat/ref'
9090

9191
export interface Renderer<HostElement = RendererElement> {
9292
render: RootRenderFunction<HostElement>
@@ -309,34 +309,34 @@ export const setRef = (
309309
rawRef: VNodeNormalizedRef,
310310
oldRawRef: VNodeNormalizedRef | null,
311311
parentSuspense: SuspenseBoundary | null,
312-
vnode: VNode | null
312+
vnode: VNode,
313+
isUnmount = false
313314
) => {
314315
if (isArray(rawRef)) {
315316
rawRef.forEach((r, i) =>
316317
setRef(
317318
r,
318319
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
319320
parentSuspense,
320-
vnode
321+
vnode,
322+
isUnmount
321323
)
322324
)
323325
return
324326
}
325327

326-
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
327-
if (!vnode) {
328-
// means unmount
329-
value = null
330-
} else if (isAsyncWrapper(vnode)) {
328+
if (isAsyncWrapper(vnode) && !isUnmount) {
331329
// when mounting async components, nothing needs to be done,
332330
// because the template ref is forwarded to inner component
333331
return
334-
} else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
335-
value = vnode.component!.exposed || vnode.component!.proxy
336-
} else {
337-
value = vnode.el
338332
}
339333

334+
const refValue =
335+
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
336+
? vnode.component!.exposed || vnode.component!.proxy
337+
: vnode.el
338+
const value = isUnmount ? null : refValue
339+
340340
const { i: owner, r: ref } = rawRef
341341
if (__DEV__ && !owner) {
342342
warn(
@@ -349,7 +349,7 @@ export const setRef = (
349349
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
350350
const setupState = owner.setupState
351351

352-
// unset old ref
352+
// dynamic ref changed. unset old ref
353353
if (oldRef != null && oldRef !== ref) {
354354
if (isString(oldRef)) {
355355
refs[oldRef] = null
@@ -363,7 +363,11 @@ export const setRef = (
363363

364364
if (isString(ref)) {
365365
const doSet = () => {
366-
refs[ref] = value
366+
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
367+
registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
368+
} else {
369+
refs[ref] = value
370+
}
367371
if (hasOwn(setupState, ref)) {
368372
setupState[ref] = value
369373
}
@@ -584,7 +588,7 @@ function baseCreateRenderer(
584588

585589
// set ref
586590
if (ref != null && parentComponent) {
587-
setRef(ref, n1 && n1.ref, parentSuspense, n2)
591+
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
588592
}
589593
}
590594

@@ -2113,7 +2117,7 @@ function baseCreateRenderer(
21132117
} = vnode
21142118
// unset ref
21152119
if (ref != null) {
2116-
setRef(ref, null, parentSuspense, null)
2120+
setRef(ref, null, parentSuspense, vnode, true)
21172121
}
21182122

21192123
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {

packages/runtime-core/src/vnode.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { setCompiledSlotRendering } from './helpers/renderSlot'
4444
import { convertLegacyComponent } from './compat/component'
4545
import { convertLegacyVModelProps } from './compat/vModel'
4646
import { defineLegacyVNodeProperties } from './compat/renderFn'
47+
import { convertLegacyRefInFor } from './compat/ref'
4748

4849
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
4950
__isFragment: true
@@ -74,6 +75,7 @@ export type VNodeRef =
7475
export type VNodeNormalizedRefAtom = {
7576
i: ComponentInternalInstance
7677
r: VNodeRef
78+
f?: boolean // v2 compat only, refInFor marker
7779
}
7880

7981
export type VNodeNormalizedRef =
@@ -130,10 +132,12 @@ export interface VNode<
130132
* @internal
131133
*/
132134
__v_isVNode: true
135+
133136
/**
134137
* @internal
135138
*/
136139
[ReactiveFlags.SKIP]: true
140+
137141
type: VNodeTypes
138142
props: (VNodeProps & ExtraProps) | null
139143
key: string | number | null
@@ -413,7 +417,7 @@ function _createVNode(
413417

414418
const vnode: VNode = {
415419
__v_isVNode: true,
416-
[ReactiveFlags.SKIP]: true,
420+
__v_skip: true,
417421
type,
418422
props,
419423
key: props && normalizeKey(props),
@@ -473,6 +477,7 @@ function _createVNode(
473477

474478
if (__COMPAT__) {
475479
convertLegacyVModelProps(vnode)
480+
convertLegacyRefInFor(vnode)
476481
defineLegacyVNodeProperties(vnode)
477482
}
478483

@@ -490,7 +495,7 @@ export function cloneVNode<T, U>(
490495
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
491496
const cloned: VNode = {
492497
__v_isVNode: true,
493-
[ReactiveFlags.SKIP]: true,
498+
__v_skip: true,
494499
type: vnode.type,
495500
props: mergedProps,
496501
key: mergedProps && normalizeKey(mergedProps),

0 commit comments

Comments
 (0)