]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: support ref in v-for, remove compat deprecation warnings
authorEvan You <yyx990803@gmail.com>
Fri, 10 Dec 2021 15:49:01 +0000 (23:49 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 10 Dec 2021 15:49:01 +0000 (23:49 +0800)
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/compat/compatConfig.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/runtime-core/__tests__/apiTemplateRef.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/ref.ts [deleted file]
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/shared/src/index.ts
packages/vue-compat/__tests__/refInfor.spec.ts [deleted file]

index b72533ee3f98084702a8b3d874acbf3a1926f720..138fa1005c4630586ec837a2b61d3d130dab2394 100644 (file)
@@ -936,7 +936,7 @@ describe('compiler: element transform', () => {
       expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
     })
 
-    test('the binding exists (inline ref input)', () => {
+    test('script setup inline mode template ref (binding exists)', () => {
       const { node } = parseWithElementTransform(`<input ref="input"/>`, {
         inline: true,
         bindingMetadata: {
@@ -949,104 +949,32 @@ describe('compiler: element transform', () => {
           {
             type: NodeTypes.JS_PROPERTY,
             key: {
-              type: NodeTypes.SIMPLE_EXPRESSION,
-              content: 'ref',
+              content: 'ref_key',
               isStatic: true
             },
             value: {
-              type: NodeTypes.JS_FUNCTION_EXPRESSION,
-              params: ['_value', '_refs'],
-              body: {
-                type: NodeTypes.JS_BLOCK_STATEMENT,
-                body: [
-                  {
-                    content: `_refs['input'] = _value`
-                  },
-                  {
-                    content: 'input.value = _value'
-                  }
-                ]
-              }
-            }
-          }
-        ]
-      })
-    })
-
-    test('the binding not exists (inline ref input)', () => {
-      const { node } = parseWithElementTransform(`<input ref="input"/>`, {
-        inline: true
-      })
-      expect(node.props).toMatchObject({
-        type: NodeTypes.JS_OBJECT_EXPRESSION,
-        properties: [
-          {
-            type: NodeTypes.JS_PROPERTY,
-            key: {
-              type: NodeTypes.SIMPLE_EXPRESSION,
-              content: 'ref',
+              content: 'input',
               isStatic: true
-            },
-            value: {
-              type: NodeTypes.JS_FUNCTION_EXPRESSION,
-              params: ['_value', '_refs'],
-              body: {
-                type: NodeTypes.JS_BLOCK_STATEMENT,
-                body: [
-                  {
-                    content: `_refs['input'] = _value`
-                  }
-                ]
-              }
             }
-          }
-        ]
-      })
-    })
-
-    test('the binding not exists (inline maybe ref input)', () => {
-      const { node } = parseWithElementTransform(`<input ref="input"/>`, {
-        inline: true,
-        bindingMetadata: {
-          input: BindingTypes.SETUP_MAYBE_REF
-        }
-      })
-      expect(node.props).toMatchObject({
-        type: NodeTypes.JS_OBJECT_EXPRESSION,
-        properties: [
+          },
           {
             type: NodeTypes.JS_PROPERTY,
             key: {
-              type: NodeTypes.SIMPLE_EXPRESSION,
               content: 'ref',
               isStatic: true
             },
             value: {
-              type: NodeTypes.JS_FUNCTION_EXPRESSION,
-              params: ['_value', '_refs'],
-              body: {
-                type: NodeTypes.JS_BLOCK_STATEMENT,
-                body: [
-                  {
-                    content: `_refs['input'] = _value`
-                  },
-                  {
-                    content: '_isRef(input) && (input.value = _value)'
-                  }
-                ]
-              }
+              content: 'input',
+              isStatic: false
             }
           }
         ]
       })
     })
 
-    test('the binding not exists (inline let ref input)', () => {
+    test('script setup inline mode template ref (binding does not exist)', () => {
       const { node } = parseWithElementTransform(`<input ref="input"/>`, {
-        inline: true,
-        bindingMetadata: {
-          input: BindingTypes.SETUP_LET
-        }
+        inline: true
       })
       expect(node.props).toMatchObject({
         type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -1054,25 +982,12 @@ describe('compiler: element transform', () => {
           {
             type: NodeTypes.JS_PROPERTY,
             key: {
-              type: NodeTypes.SIMPLE_EXPRESSION,
               content: 'ref',
               isStatic: true
             },
             value: {
-              type: NodeTypes.JS_FUNCTION_EXPRESSION,
-              params: ['_value', '_refs'],
-              body: {
-                type: NodeTypes.JS_BLOCK_STATEMENT,
-                body: [
-                  {
-                    content: `_refs['input'] = _value`
-                  },
-                  {
-                    content:
-                      '_isRef(input) ? input.value = _value : input = _value'
-                  }
-                ]
-              }
+              content: 'input',
+              isStatic: true
             }
           }
         ]
index d43c441d39a7f679932274098ed0de1d7812fdc0..717b8dc46ad8a50e55dbfe3887019a7edd65499b 100644 (file)
@@ -20,7 +20,6 @@ export const enum CompilerDeprecationTypes {
   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'
@@ -79,13 +78,6 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
     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 ` +
index e79a560a2b07886183debaa1ec3df1361c1ae2d1..47cb3b0f48f005044cda9a80894fb1c550029ea5 100644 (file)
@@ -19,10 +19,7 @@ import {
   TemplateTextChildNode,
   DirectiveArguments,
   createVNodeCall,
-  ConstantTypes,
-  JSChildNode,
-  createFunctionExpression,
-  createBlockStatement
+  ConstantTypes
 } from '../ast'
 import {
   PatchFlags,
@@ -48,8 +45,7 @@ import {
   KEEP_ALIVE,
   SUSPENSE,
   UNREF,
-  GUARD_REACTIVE_PROPS,
-  IS_REF
+  GUARD_REACTIVE_PROPS
 } from '../runtimeHelpers'
 import {
   getInnerRange,
@@ -467,20 +463,32 @@ export function buildProps(
     const prop = props[i]
     if (prop.type === NodeTypes.ATTRIBUTE) {
       const { loc, name, value } = prop
-      let valueNode = createSimpleExpression(
-        value ? value.content : '',
-        true,
-        value ? value.loc : loc
-      ) as JSChildNode
+      let isStatic = true
       if (name === 'ref') {
         hasRef = true
+        if (context.scopes.vFor > 0) {
+          properties.push(
+            createObjectProperty(
+              createSimpleExpression('ref_for', true),
+              createSimpleExpression('true')
+            )
+          )
+        }
         // 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
         // actual ref instead.
-        if (!__BROWSER__ && context.inline && value?.content) {
-          valueNode = createFunctionExpression(['_value', '_refs'])
-          valueNode.body = createBlockStatement(
-            processInlineRef(context, value.content)
+        if (
+          !__BROWSER__ &&
+          value &&
+          context.inline &&
+          context.bindingMetadata[value.content]
+        ) {
+          isStatic = false
+          properties.push(
+            createObjectProperty(
+              createSimpleExpression('ref_key', true),
+              createSimpleExpression(value.content, true, value.loc)
+            )
           )
         }
       }
@@ -504,7 +512,11 @@ export function buildProps(
             true,
             getInnerRange(loc, 0, name.length)
           ),
-          valueNode
+          createSimpleExpression(
+            value ? value.content : '',
+            isStatic,
+            value ? value.loc : loc
+          )
         )
       )
     } else {
@@ -555,6 +567,15 @@ export function buildProps(
         shouldUseBlock = true
       }
 
+      if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
+        properties.push(
+          createObjectProperty(
+            createSimpleExpression('ref_for', true),
+            createSimpleExpression('true')
+          )
+        )
+      }
+
       // special case for v-bind and v-on with no argument
       if (!arg && (isVBind || isVOn)) {
         hasDynamicKeys = true
@@ -654,25 +675,6 @@ export function buildProps(
         }
       }
     }
-
-    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
@@ -914,30 +916,3 @@ function stringifyDynamicPropNames(props: string[]): string {
 function isComponentTag(tag: string) {
   return tag === 'component' || tag === 'Component'
 }
-
-function processInlineRef(
-  context: TransformContext,
-  raw: string
-): JSChildNode[] {
-  const body = [createSimpleExpression(`_refs['${raw}'] = _value`)]
-  const { bindingMetadata, helperString } = context
-  const type = bindingMetadata[raw]
-  if (type === BindingTypes.SETUP_REF) {
-    body.push(createSimpleExpression(`${raw}.value = _value`))
-  } else if (type === BindingTypes.SETUP_MAYBE_REF) {
-    body.push(
-      createSimpleExpression(
-        `${helperString(IS_REF)}(${raw}) && (${raw}.value = _value)`
-      )
-    )
-  } else if (type === BindingTypes.SETUP_LET) {
-    body.push(
-      createSimpleExpression(
-        `${helperString(
-          IS_REF
-        )}(${raw}) ? ${raw}.value = _value : ${raw} = _value`
-      )
-    )
-  }
-  return body
-}
index 2c30b58add3dd21615be203806755543b945abb6..198687c06db7677d76239a770a12308e14f4fbe2 100644 (file)
@@ -365,4 +365,81 @@ describe('api: template refs', () => {
     expect(elRef1.value).toBeNull()
     expect(elRef1.value).toBe(elRef2.value)
   })
+
+  // compiled output of <script setup> inline mode
+  test('raw ref with ref_key', () => {
+    let refs: any
+
+    const el = ref()
+
+    const App = {
+      mounted() {
+        refs = (this as any).$refs
+      },
+      render() {
+        return h(
+          'div',
+          {
+            ref: el,
+            ref_key: 'el'
+          },
+          'hello'
+        )
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+
+    expect(serializeInner(el.value)).toBe('hello')
+    expect(serializeInner(refs.el)).toBe('hello')
+  })
+
+  // compiled output of v-for + template ref
+  test('ref in v-for', async () => {
+    const show = ref(true)
+    const list = reactive([1, 2, 3])
+    const listRefs = ref([])
+    const mapRefs = () => listRefs.value.map(n => serializeInner(n))
+
+    const App = {
+      render() {
+        return show.value
+          ? h(
+              'ul',
+              list.map(i =>
+                h(
+                  'li',
+                  {
+                    ref: listRefs,
+                    ref_for: true
+                  },
+                  i
+                )
+              )
+            )
+          : null
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+
+    expect(mapRefs()).toMatchObject(['1', '2', '3'])
+
+    list.push(4)
+    await nextTick()
+    expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
+
+    list.shift()
+    await nextTick()
+    expect(mapRefs()).toMatchObject(['2', '3', '4'])
+
+    show.value = !show.value
+    await nextTick()
+
+    expect(mapRefs()).toMatchObject([])
+
+    show.value = !show.value
+    await nextTick()
+    expect(mapRefs()).toMatchObject(['2', '3', '4'])
+  })
 })
index a38c93140aad612ecb6f1a3d07e23110c0152502..a8979e811dc940be705ead8824ed6e733d38abe2 100644 (file)
@@ -12,7 +12,7 @@ import {
 } from '../src/vnode'
 import { Data } from '../src/component'
 import { ShapeFlags, PatchFlags } from '@vue/shared'
-import { h, reactive, isReactive, setBlockTracking } from '../src'
+import { h, reactive, isReactive, setBlockTracking, ref } from '../src'
 import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
 import { setCurrentRenderingInstance } from '../src/componentRenderContext'
 
@@ -236,20 +236,24 @@ describe('vnode', () => {
 
     setCurrentRenderingInstance(mockInstance1)
     const original = createVNode('div', { ref: 'foo' })
-    expect(original.ref).toStrictEqual({ i: mockInstance1, r: 'foo' })
+    expect(original.ref).toMatchObject({
+      i: mockInstance1,
+      r: 'foo',
+      f: false
+    })
 
     // clone and preserve original ref
     const cloned1 = cloneVNode(original)
-    expect(cloned1.ref).toStrictEqual({ i: mockInstance1, r: 'foo' })
+    expect(cloned1.ref).toMatchObject({ i: mockInstance1, r: 'foo', f: false })
 
     // cloning with new ref, but with same context instance
     const cloned2 = cloneVNode(original, { ref: 'bar' })
-    expect(cloned2.ref).toStrictEqual({ i: mockInstance1, r: 'bar' })
+    expect(cloned2.ref).toMatchObject({ i: mockInstance1, r: 'bar', f: false })
 
     // cloning and adding ref to original that has no ref
     const original2 = createVNode('div')
     const cloned3 = cloneVNode(original2, { ref: 'bar' })
-    expect(cloned3.ref).toStrictEqual({ i: mockInstance1, r: 'bar' })
+    expect(cloned3.ref).toMatchObject({ i: mockInstance1, r: 'bar', f: false })
 
     // cloning with different context instance
     setCurrentRenderingInstance(mockInstance2)
@@ -257,16 +261,35 @@ describe('vnode', () => {
     // clone and preserve original ref
     const cloned4 = cloneVNode(original)
     // #1311 should preserve original context instance!
-    expect(cloned4.ref).toStrictEqual({ i: mockInstance1, r: 'foo' })
+    expect(cloned4.ref).toMatchObject({ i: mockInstance1, r: 'foo', f: false })
 
     // cloning with new ref, but with same context instance
     const cloned5 = cloneVNode(original, { ref: 'bar' })
     // new ref should use current context instance and overwrite original
-    expect(cloned5.ref).toStrictEqual({ i: mockInstance2, r: 'bar' })
+    expect(cloned5.ref).toMatchObject({ i: mockInstance2, r: 'bar', f: false })
 
     // cloning and adding ref to original that has no ref
     const cloned6 = cloneVNode(original2, { ref: 'bar' })
-    expect(cloned6.ref).toStrictEqual({ i: mockInstance2, r: 'bar' })
+    expect(cloned6.ref).toMatchObject({ i: mockInstance2, r: 'bar', f: false })
+
+    const original3 = createVNode('div', { ref: 'foo', ref_for: true })
+    expect(original3.ref).toMatchObject({
+      i: mockInstance2,
+      r: 'foo',
+      f: true
+    })
+    const cloned7 = cloneVNode(original3, { ref: 'bar', ref_for: true })
+    expect(cloned7.ref).toMatchObject({ i: mockInstance2, r: 'bar', f: true })
+
+    const r = ref()
+    const original4 = createVNode('div', { ref: r, ref_key: 'foo' })
+    expect(original4.ref).toMatchObject({
+      i: mockInstance2,
+      r,
+      k: 'foo'
+    })
+    const cloned8 = cloneVNode(original4)
+    expect(cloned8.ref).toMatchObject({ i: mockInstance2, r, k: 'foo' })
 
     setCurrentRenderingInstance(null)
   })
@@ -277,14 +300,14 @@ describe('vnode', () => {
 
     setCurrentRenderingInstance(mockInstance1)
     const original = createVNode('div', { ref: 'foo' })
-    expect(original.ref).toStrictEqual({ i: mockInstance1, r: 'foo' })
+    expect(original.ref).toMatchObject({ i: mockInstance1, r: 'foo', f: false })
 
     // clone and preserve original ref
     setCurrentRenderingInstance(mockInstance2)
     const cloned1 = cloneVNode(original, { ref: 'bar' }, true)
-    expect(cloned1.ref).toStrictEqual([
-      { i: mockInstance1, r: 'foo' },
-      { i: mockInstance2, r: 'bar' }
+    expect(cloned1.ref).toMatchObject([
+      { i: mockInstance1, r: 'foo', f: false },
+      { i: mockInstance2, r: 'bar', f: false }
     ])
 
     setCurrentRenderingInstance(null)
index c1c32a1fa98e19d929441bb5a9e25e47ef272799..e03b8aa5aa670097f40b65ecd184e7afe1e30de9 100644 (file)
@@ -46,7 +46,6 @@ export const enum DeprecationTypes {
   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',
 
@@ -298,13 +297,6 @@ export const deprecationData: Record<DeprecationTypes, DeprecationData> = {
     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. ` +
diff --git a/packages/runtime-core/src/compat/ref.ts b/packages/runtime-core/src/compat/ref.ts
deleted file mode 100644 (file)
index 7b82712..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-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
-  }
-}
index 3892eebee8d3a243bdee289a60a3b35c39aeee4f..073ff91be10a0d982d43149d36d4502a336cfb46 100644 (file)
@@ -40,7 +40,8 @@ import {
   hasOwn,
   invokeArrayFns,
   isArray,
-  getGlobalThis
+  getGlobalThis,
+  remove
 } from '@vue/shared'
 import {
   queueJob,
@@ -86,7 +87,6 @@ import { initFeatureFlags } from './featureFlags'
 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>
@@ -2407,40 +2407,53 @@ export function setRef(
     }
   }
 
-  if (isString(ref)) {
-    const doSet = () => {
-      if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
-        registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
-      } else {
-        refs[ref] = value
+  if (isFunction(ref)) {
+    callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
+  } else {
+    const _isString = isString(ref)
+    const _isRef = isRef(ref)
+    if (_isString || _isRef) {
+      const doSet = () => {
+        if (rawRef.f) {
+          const existing = _isString ? refs[ref] : ref.value
+          if (isUnmount) {
+            isArray(existing) && remove(existing, refValue)
+          } else {
+            if (!isArray(existing)) {
+              if (_isString) {
+                refs[ref] = [refValue]
+              } else {
+                ref.value = [refValue]
+                if (rawRef.k) refs[rawRef.k] = ref.value
+              }
+            } else if (!existing.includes(refValue)) {
+              existing.push(refValue)
+            }
+          }
+        } else if (_isString) {
+          refs[ref] = value
+          if (hasOwn(setupState, ref)) {
+            setupState[ref] = value
+          }
+        } else if (isRef(ref)) {
+          ref.value = value
+          if (rawRef.k) refs[rawRef.k] = value
+        } else if (__DEV__) {
+          warn('Invalid template ref type:', ref, `(${typeof ref})`)
+        }
       }
-      if (hasOwn(setupState, ref)) {
-        setupState[ref] = value
+      if (value) {
+        // #1789: for non-null values, set them after render
+        // null values means this is unmount and it should not overwrite another
+        // ref with the same key
+        ;(doSet as SchedulerJob).id = -1
+        queuePostRenderEffect(doSet, parentSuspense)
+      } else {
+        doSet()
       }
+    } else if (__DEV__) {
+      warn('Invalid template ref type:', ref, `(${typeof ref})`)
     }
-    // #1789: for non-null values, set them after render
-    // null values means this is unmount and it should not overwrite another
-    // ref with the same key
-    if (value) {
-      ;(doSet as SchedulerJob).id = -1
-      queuePostRenderEffect(doSet, parentSuspense)
-    } else {
-      doSet()
-    }
-  } else if (isRef(ref)) {
-    const doSet = () => {
-      ref.value = value
-    }
-    if (value) {
-      ;(doSet as SchedulerJob).id = -1
-      queuePostRenderEffect(doSet, parentSuspense)
-    } else {
-      doSet()
-    }
-  } else if (isFunction(ref)) {
-    callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
-  } else if (__DEV__) {
-    warn('Invalid template ref type:', value, `(${typeof value})`)
   }
 }
 
index 306afbcc3d24c3b782f43c5a5d1dd32560f97ea2..a70d93e39770b55e2e2cc943b4381a2406acae23 100644 (file)
@@ -42,7 +42,6 @@ import { hmrDirtyComponents } from './hmr'
 import { convertLegacyComponent } from './compat/component'
 import { convertLegacyVModelProps } from './compat/componentVModel'
 import { defineLegacyVNodeProperties } from './compat/renderFn'
-import { convertLegacyRefInFor } from './compat/ref'
 
 export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined) as any as {
   __isFragment: true
@@ -73,7 +72,8 @@ export type VNodeRef =
 export type VNodeNormalizedRefAtom = {
   i: ComponentInternalInstance
   r: VNodeRef
-  f?: boolean // v2 compat only, refInFor marker
+  k?: string // setup ref key
+  f?: boolean // refInFor marker
 }
 
 export type VNodeNormalizedRef =
@@ -92,6 +92,8 @@ export type VNodeHook =
 export type VNodeProps = {
   key?: string | number | symbol
   ref?: VNodeRef
+  ref_for?: boolean
+  ref_key?: string
 
   // vnode hooks
   onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
@@ -380,11 +382,15 @@ export const InternalObjectKey = `__vInternal`
 const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
   key != null ? key : null
 
-const normalizeRef = ({ ref }: VNodeProps): VNodeNormalizedRefAtom | null => {
+const normalizeRef = ({
+  ref,
+  ref_key,
+  ref_for
+}: VNodeProps): VNodeNormalizedRefAtom | null => {
   return (
     ref != null
       ? isString(ref) || isRef(ref) || isFunction(ref)
-        ? { i: currentRenderingInstance, r: ref }
+        ? { i: currentRenderingInstance, r: ref, k: ref_key, f: !!ref_for }
         : ref
       : null
   ) as any
@@ -468,7 +474,6 @@ function createBaseVNode(
 
   if (__COMPAT__) {
     convertLegacyVModelProps(vnode)
-    convertLegacyRefInFor(vnode)
     defineLegacyVNodeProperties(vnode)
   }
 
index 97750a81cd99b0c6f051e14b5e534769a42e1700..c41a1a5fad51636bb62855b4415a7051e80e5c17 100644 (file)
@@ -83,7 +83,7 @@ export const isIntegerKey = (key: unknown) =>
 
 export const isReservedProp = /*#__PURE__*/ makeMap(
   // the leading comma is intentional so empty string "" is also included
-  ',key,ref,' +
+  ',key,ref,ref_for,ref_key,' +
     'onVnodeBeforeMount,onVnodeMounted,' +
     'onVnodeBeforeUpdate,onVnodeUpdated,' +
     'onVnodeBeforeUnmount,onVnodeUnmounted'
diff --git a/packages/vue-compat/__tests__/refInfor.spec.ts b/packages/vue-compat/__tests__/refInfor.spec.ts
deleted file mode 100644 (file)
index af1a78a..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import Vue from '@vue/compat'
-import { nextTick } from '../../runtime-core/src/scheduler'
-import {
-  DeprecationTypes,
-  deprecationData,
-  toggleDeprecationWarning
-} from '../../runtime-core/src/compat/compatConfig'
-
-beforeEach(() => {
-  toggleDeprecationWarning(true)
-  Vue.configureCompat({
-    MODE: 2,
-    GLOBAL_MOUNT: 'suppress-warning'
-  })
-})
-
-afterEach(() => {
-  toggleDeprecationWarning(false)
-  Vue.configureCompat({ MODE: 3 })
-})
-
-test('V_FOR_REF', async () => {
-  const vm = new Vue({
-    data() {
-      return {
-        ok: true,
-        list: [1, 2, 3]
-      }
-    },
-    template: `
-    <template v-if="ok">
-      <li v-for="i in list" ref="list">{{ i }}</li>
-    </template>
-    `
-  }).$mount() as any
-
-  const mapRefs = () => vm.$refs.list.map((el: HTMLElement) => el.textContent)
-  expect(mapRefs()).toMatchObject(['1', '2', '3'])
-
-  expect(deprecationData[DeprecationTypes.V_FOR_REF].message).toHaveBeenWarned()
-
-  vm.list.push(4)
-  await nextTick()
-  expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
-
-  vm.list.shift()
-  await nextTick()
-  expect(mapRefs()).toMatchObject(['2', '3', '4'])
-
-  vm.ok = !vm.ok
-  await nextTick()
-  expect(mapRefs()).toMatchObject([])
-
-  vm.ok = !vm.ok
-  await nextTick()
-  expect(mapRefs()).toMatchObject(['2', '3', '4'])
-})