wip: ref v-for compat

This commit is contained in:
Evan You 2021-04-26 17:35:41 -04:00
parent 3e815be24e
commit 86703c23a6
7 changed files with 117 additions and 28 deletions

View File

@ -20,6 +20,7 @@ export const enum CompilerDeprecationTypes {
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER', COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE', COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE', 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_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE', COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
COMPILER_FILTERS = 'COMPILER_FILTER' COMPILER_FILTERS = 'COMPILER_FILTER'
@ -78,6 +79,13 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
link: `https://v3.vuejs.org/guide/migration/v-if-v-for.html` 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]: { [CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
message: message:
`<template> with no special directives will render as a native template ` + `<template> with no special directives will render as a native template ` +

View File

@ -590,6 +590,25 @@ export function buildProps(
runtimeDirectives.push(prop) 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 let propsExpression: PropsExpression | undefined = undefined

View File

@ -44,6 +44,7 @@ export const enum DeprecationTypes {
WATCH_ARRAY = 'WATCH_ARRAY', WATCH_ARRAY = 'WATCH_ARRAY',
PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS', PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS',
V_FOR_REF = 'V_FOR_REF',
V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER', V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER',
CUSTOM_DIR = 'CUSTOM_DIR', CUSTOM_DIR = 'CUSTOM_DIR',
@ -287,6 +288,13 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
link: `https://v3.vuejs.org/guide/migration/custom-directives.html` 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]: { [DeprecationTypes.V_ON_KEYCODE_MODIFIER]: {
message: message:
`Using keyCode as v-on modifier is no longer supported. ` + `Using keyCode as v-on modifier is no longer supported. ` +
@ -447,16 +455,18 @@ export function warnDeprecation(
} }
const dupKey = key + args.join('') 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 // skip if the same warning is emitted for the same component type
if (compName !== `Anonymous`) { const componentDupKey = dupKey + compId
const componentDupKey = dupKey + compName if (componentDupKey in instanceWarned) {
if (componentDupKey in instanceWarned) { return
return
}
instanceWarned[componentDupKey] = true
} }
instanceWarned[componentDupKey] = true
// same warning, but different component. skip the long message and just // same warning, but different component. skip the long message and just
// log the key and count. // log the key and count.

View File

@ -0,0 +1,45 @@
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
}
}

View File

@ -171,7 +171,7 @@ export function compatH(
} }
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap( const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
'refInFor,staticStyle,staticClass,directives,model' 'staticStyle,staticClass,directives,model,hook'
) )
function convertLegacyProps( function convertLegacyProps(
@ -206,8 +206,6 @@ function convertLegacyProps(
} }
} }
} }
} else if (key === 'hook') {
// TODO
} else if (!skipLegacyRootLevelProps(key)) { } else if (!skipLegacyRootLevelProps(key)) {
converted[key] = legacyProps[key as keyof LegacyVNodeProps] converted[key] = legacyProps[key as keyof LegacyVNodeProps]
} }

View File

@ -76,7 +76,6 @@ import {
import { createHydrationFunctions, RootHydrateFunction } from './hydration' import { createHydrationFunctions, RootHydrateFunction } from './hydration'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { startMeasure, endMeasure } from './profiling' import { startMeasure, endMeasure } from './profiling'
import { ComponentPublicInstance } from './componentPublicInstance'
import { import {
devtoolsComponentAdded, devtoolsComponentAdded,
devtoolsComponentRemoved, devtoolsComponentRemoved,
@ -87,6 +86,7 @@ import { initFeatureFlags } from './featureFlags'
import { isAsyncWrapper } from './apiAsyncComponent' import { isAsyncWrapper } from './apiAsyncComponent'
import { isCompatEnabled } from './compat/compatConfig' import { isCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig'
import { registerLegacyRef } from './compat/ref'
export interface Renderer<HostElement = RendererElement> { export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement> render: RootRenderFunction<HostElement>
@ -309,7 +309,8 @@ export const setRef = (
rawRef: VNodeNormalizedRef, rawRef: VNodeNormalizedRef,
oldRawRef: VNodeNormalizedRef | null, oldRawRef: VNodeNormalizedRef | null,
parentSuspense: SuspenseBoundary | null, parentSuspense: SuspenseBoundary | null,
vnode: VNode | null vnode: VNode,
isUnmount = false
) => { ) => {
if (isArray(rawRef)) { if (isArray(rawRef)) {
rawRef.forEach((r, i) => rawRef.forEach((r, i) =>
@ -317,26 +318,25 @@ export const setRef = (
r, r,
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
parentSuspense, parentSuspense,
vnode vnode,
isUnmount
) )
) )
return return
} }
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null if (isAsyncWrapper(vnode) && !isUnmount) {
if (!vnode) {
// means unmount
value = null
} else if (isAsyncWrapper(vnode)) {
// when mounting async components, nothing needs to be done, // when mounting async components, nothing needs to be done,
// because the template ref is forwarded to inner component // because the template ref is forwarded to inner component
return 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 const { i: owner, r: ref } = rawRef
if (__DEV__ && !owner) { if (__DEV__ && !owner) {
warn( warn(
@ -349,7 +349,7 @@ export const setRef = (
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
const setupState = owner.setupState const setupState = owner.setupState
// unset old ref // dynamic ref changed. unset old ref
if (oldRef != null && oldRef !== ref) { if (oldRef != null && oldRef !== ref) {
if (isString(oldRef)) { if (isString(oldRef)) {
refs[oldRef] = null refs[oldRef] = null
@ -363,7 +363,11 @@ export const setRef = (
if (isString(ref)) { if (isString(ref)) {
const doSet = () => { 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)) { if (hasOwn(setupState, ref)) {
setupState[ref] = value setupState[ref] = value
} }
@ -584,7 +588,7 @@ function baseCreateRenderer(
// set ref // set ref
if (ref != null && parentComponent) { if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2) setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
} }
} }
@ -2113,7 +2117,7 @@ function baseCreateRenderer(
} = vnode } = vnode
// unset ref // unset ref
if (ref != null) { if (ref != null) {
setRef(ref, null, parentSuspense, null) setRef(ref, null, parentSuspense, vnode, true)
} }
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {

View File

@ -44,6 +44,7 @@ import { setCompiledSlotRendering } from './helpers/renderSlot'
import { convertLegacyComponent } from './compat/component' import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/vModel' import { convertLegacyVModelProps } from './compat/vModel'
import { defineLegacyVNodeProperties } from './compat/renderFn' import { defineLegacyVNodeProperties } from './compat/renderFn'
import { convertLegacyRefInFor } from './compat/ref'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true __isFragment: true
@ -74,6 +75,7 @@ export type VNodeRef =
export type VNodeNormalizedRefAtom = { export type VNodeNormalizedRefAtom = {
i: ComponentInternalInstance i: ComponentInternalInstance
r: VNodeRef r: VNodeRef
f?: boolean // v2 compat only, refInFor marker
} }
export type VNodeNormalizedRef = export type VNodeNormalizedRef =
@ -130,10 +132,12 @@ export interface VNode<
* @internal * @internal
*/ */
__v_isVNode: true __v_isVNode: true
/** /**
* @internal * @internal
*/ */
[ReactiveFlags.SKIP]: true [ReactiveFlags.SKIP]: true
type: VNodeTypes type: VNodeTypes
props: (VNodeProps & ExtraProps) | null props: (VNodeProps & ExtraProps) | null
key: string | number | null key: string | number | null
@ -413,7 +417,7 @@ function _createVNode(
const vnode: VNode = { const vnode: VNode = {
__v_isVNode: true, __v_isVNode: true,
[ReactiveFlags.SKIP]: true, __v_skip: true,
type, type,
props, props,
key: props && normalizeKey(props), key: props && normalizeKey(props),
@ -473,6 +477,7 @@ function _createVNode(
if (__COMPAT__) { if (__COMPAT__) {
convertLegacyVModelProps(vnode) convertLegacyVModelProps(vnode)
convertLegacyRefInFor(vnode)
defineLegacyVNodeProperties(vnode) defineLegacyVNodeProperties(vnode)
} }
@ -490,7 +495,7 @@ export function cloneVNode<T, U>(
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
const cloned: VNode = { const cloned: VNode = {
__v_isVNode: true, __v_isVNode: true,
[ReactiveFlags.SKIP]: true, __v_skip: true,
type: vnode.type, type: vnode.type,
props: mergedProps, props: mergedProps,
key: mergedProps && normalizeKey(mergedProps), key: mergedProps && normalizeKey(mergedProps),