COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
+ COMPILER_V_FOR_REF = 'COMPILER_V_FOR_REF',
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
COMPILER_FILTERS = 'COMPILER_FILTER'
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html`
},
+ [CompilerDeprecationTypes.COMPILER_V_FOR_REF]: {
+ message:
+ `Ref usage on v-for no longer creates array ref values in Vue 3. ` +
+ `Consider using function refs or refactor to avoid ref usage altogether.`,
+ link: `https://v3.vuejs.org/guide/migration/array-refs.html`
+ },
+
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
message:
`<template> with no special directives will render as a native template ` +
runtimeDirectives.push(prop)
}
}
+
+ if (
+ __COMPAT__ &&
+ prop.type === NodeTypes.ATTRIBUTE &&
+ prop.name === 'ref' &&
+ context.scopes.vFor > 0 &&
+ checkCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_V_FOR_REF,
+ context,
+ prop.loc
+ )
+ ) {
+ properties.push(
+ createObjectProperty(
+ createSimpleExpression('refInFor', true),
+ createSimpleExpression('true', false)
+ )
+ )
+ }
}
let propsExpression: PropsExpression | undefined = undefined
WATCH_ARRAY = 'WATCH_ARRAY',
PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS',
+ V_FOR_REF = 'V_FOR_REF',
V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER',
CUSTOM_DIR = 'CUSTOM_DIR',
link: `https://v3.vuejs.org/guide/migration/custom-directives.html`
},
+ [DeprecationTypes.V_FOR_REF]: {
+ message:
+ `Ref usage on v-for no longer creates array ref values in Vue 3. ` +
+ `Consider using function refs or refactor to avoid ref usage altogether.`,
+ link: `https://v3.vuejs.org/guide/migration/array-refs.html`
+ },
+
[DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
message:
`Using keyCode as v-on modifier is no longer supported. ` +
}
const dupKey = key + args.join('')
- const compName = instance && formatComponentName(instance, instance.type)
+ let compId: string | number | null =
+ instance && formatComponentName(instance, instance.type)
+ if (compId === 'Anonymous' && instance) {
+ compId = instance.uid
+ }
// skip if the same warning is emitted for the same component type
- if (compName !== `Anonymous`) {
- const componentDupKey = dupKey + compName
- if (componentDupKey in instanceWarned) {
- return
- }
- instanceWarned[componentDupKey] = true
+ const componentDupKey = dupKey + compId
+ if (componentDupKey in instanceWarned) {
+ return
}
+ instanceWarned[componentDupKey] = true
// same warning, but different component. skip the long message and just
// log the key and count.
--- /dev/null
+import { isArray, remove } from '@vue/shared'
+import { ComponentInternalInstance, Data } from '../component'
+import { VNode } from '../vnode'
+import { DeprecationTypes, warnDeprecation } from './compatConfig'
+
+export function convertLegacyRefInFor(vnode: VNode) {
+ // refInFor
+ if (vnode.props && vnode.props.refInFor) {
+ delete vnode.props.refInFor
+ if (vnode.ref) {
+ if (isArray(vnode.ref)) {
+ vnode.ref.forEach(r => (r.f = true))
+ } else {
+ vnode.ref.f = true
+ }
+ }
+ }
+}
+
+export function registerLegacyRef(
+ refs: Data,
+ key: string,
+ value: any,
+ owner: ComponentInternalInstance,
+ isInFor: boolean | undefined,
+ isUnmount: boolean
+) {
+ const existing = refs[key]
+ if (isUnmount) {
+ if (isArray(existing)) {
+ remove(existing, value)
+ } else {
+ refs[key] = null
+ }
+ } else if (isInFor) {
+ __DEV__ && warnDeprecation(DeprecationTypes.V_FOR_REF, owner)
+ if (!isArray(existing)) {
+ refs[key] = [value]
+ } else if (!existing.includes(value)) {
+ existing.push(value)
+ }
+ } else {
+ refs[key] = value
+ }
+}
}
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
- 'refInFor,staticStyle,staticClass,directives,model'
+ 'staticStyle,staticClass,directives,model,hook'
)
function convertLegacyProps(
}
}
}
- } else if (key === 'hook') {
- // TODO
} else if (!skipLegacyRootLevelProps(key)) {
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
}
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
import { invokeDirectiveHook } from './directives'
import { startMeasure, endMeasure } from './profiling'
-import { ComponentPublicInstance } from './componentPublicInstance'
import {
devtoolsComponentAdded,
devtoolsComponentRemoved,
import { isAsyncWrapper } from './apiAsyncComponent'
import { isCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
+import { registerLegacyRef } from './compat/ref'
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
rawRef: VNodeNormalizedRef,
oldRawRef: VNodeNormalizedRef | null,
parentSuspense: SuspenseBoundary | null,
- vnode: VNode | null
+ vnode: VNode,
+ isUnmount = false
) => {
if (isArray(rawRef)) {
rawRef.forEach((r, i) =>
r,
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
parentSuspense,
- vnode
+ vnode,
+ isUnmount
)
)
return
}
- let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
- if (!vnode) {
- // means unmount
- value = null
- } else if (isAsyncWrapper(vnode)) {
+ if (isAsyncWrapper(vnode) && !isUnmount) {
// when mounting async components, nothing needs to be done,
// because the template ref is forwarded to inner component
return
- } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
- value = vnode.component!.exposed || vnode.component!.proxy
- } else {
- value = vnode.el
}
+ const refValue =
+ vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
+ ? vnode.component!.exposed || vnode.component!.proxy
+ : vnode.el
+ const value = isUnmount ? null : refValue
+
const { i: owner, r: ref } = rawRef
if (__DEV__ && !owner) {
warn(
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
const setupState = owner.setupState
- // unset old ref
+ // dynamic ref changed. unset old ref
if (oldRef != null && oldRef !== ref) {
if (isString(oldRef)) {
refs[oldRef] = null
if (isString(ref)) {
const doSet = () => {
- refs[ref] = value
+ if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
+ registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
+ } else {
+ refs[ref] = value
+ }
if (hasOwn(setupState, ref)) {
setupState[ref] = value
}
// set ref
if (ref != null && parentComponent) {
- setRef(ref, n1 && n1.ref, parentSuspense, n2)
+ setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
}
}
} = vnode
// unset ref
if (ref != null) {
- setRef(ref, null, parentSuspense, null)
+ setRef(ref, null, parentSuspense, vnode, true)
}
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/vModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
+import { convertLegacyRefInFor } from './compat/ref'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
export type VNodeNormalizedRefAtom = {
i: ComponentInternalInstance
r: VNodeRef
+ f?: boolean // v2 compat only, refInFor marker
}
export type VNodeNormalizedRef =
* @internal
*/
__v_isVNode: true
+
/**
* @internal
*/
[ReactiveFlags.SKIP]: true
+
type: VNodeTypes
props: (VNodeProps & ExtraProps) | null
key: string | number | null
const vnode: VNode = {
__v_isVNode: true,
- [ReactiveFlags.SKIP]: true,
+ __v_skip: true,
type,
props,
key: props && normalizeKey(props),
if (__COMPAT__) {
convertLegacyVModelProps(vnode)
+ convertLegacyRefInFor(vnode)
defineLegacyVNodeProperties(vnode)
}
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
const cloned: VNode = {
__v_isVNode: true,
- [ReactiveFlags.SKIP]: true,
+ __v_skip: true,
type: vnode.type,
props: mergedProps,
key: mergedProps && normalizeKey(mergedProps),