Parcourir la source

fix(useTemplateRef): don't update setup ref for useTemplateRef key (#12756)

close #12749
edison il y a 2 mois
Parent
commit
fc40ca0216

+ 47 - 0
packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts

@@ -3,6 +3,7 @@ import {
   h,
   nextTick,
   nodeOps,
+  onMounted,
   ref,
   render,
   useTemplateRef,
@@ -254,6 +255,52 @@ describe('useTemplateRef', () => {
     }
   })
 
+  // #12749
+  test(`don't update setup ref for useTemplateRef key`, () => {
+    let foo: ShallowRef
+    const Comp = {
+      setup() {
+        foo = useTemplateRef('bar')
+        const bar = ref(null)
+        onMounted(() => {
+          expect(bar.value).toBe(null)
+        })
+        return { bar }
+      },
+      render() {
+        return h('div', { ref: 'bar' })
+      },
+    }
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    expect(foo!.value).toBe(root.children[0])
+  })
+
+  test(`don't update setup ref for useTemplateRef key (compiled in prod mode)`, () => {
+    __DEV__ = false
+    try {
+      let foo: ReturnType<typeof ref>
+      let fooRef: ShallowRef
+      const Comp = {
+        setup() {
+          foo = ref('hello')
+          fooRef = useTemplateRef('foo')
+          return { foo }
+        },
+        render() {
+          return h('input', { ref: foo, ref_key: 'foo' })
+        },
+      }
+      const root = nodeOps.createElement('div')
+      render(h(Comp), root)
+
+      expect(foo!.value).toBe('hello')
+      expect(fooRef!.value).toBe(root.children[0])
+    } finally {
+      __DEV__ = true
+    }
+  })
+
   test('should work when used as direct ref value with ref_key and ref_for (compiled in prod mode)', () => {
     __DEV__ = false
     try {

+ 9 - 7
packages/runtime-core/src/helpers/useTemplateRef.ts

@@ -1,5 +1,5 @@
 import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
-import { getCurrentInstance } from '../component'
+import { type Data, getCurrentInstance } from '../component'
 import { warn } from '../warning'
 import { EMPTY_OBJ } from '@vue/shared'
 
@@ -14,12 +14,7 @@ export function useTemplateRef<T = unknown, Keys extends string = string>(
   const r = shallowRef(null)
   if (i) {
     const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
-    let desc: PropertyDescriptor | undefined
-    if (
-      __DEV__ &&
-      (desc = Object.getOwnPropertyDescriptor(refs, key)) &&
-      !desc.configurable
-    ) {
+    if (__DEV__ && isTemplateRefKey(refs, key)) {
       warn(`useTemplateRef('${key}') already exists.`)
     } else {
       Object.defineProperty(refs, key, {
@@ -40,3 +35,10 @@ export function useTemplateRef<T = unknown, Keys extends string = string>(
   }
   return ret
 }
+
+export function isTemplateRefKey(refs: Data, key: string): boolean {
+  let desc: PropertyDescriptor | undefined
+  return !!(
+    (desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable
+  )
+}

+ 20 - 9
packages/runtime-core/src/rendererTemplateRef.ts

@@ -22,7 +22,7 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import { type SchedulerJob, SchedulerJobFlags } from './scheduler'
 import { queuePostRenderEffect } from './renderer'
 import { type ComponentOptions, getComponentPublicInstance } from './component'
-import { knownTemplateRefs } from './helpers/useTemplateRef'
+import { isTemplateRefKey, knownTemplateRefs } from './helpers/useTemplateRef'
 
 const pendingSetRefMap = new WeakMap<VNodeNormalizedRef, SchedulerJob>()
 /**
@@ -98,11 +98,23 @@ export function setRef(
               return false
             }
           }
+
+          // skip setting up ref if the key is from useTemplateRef
+          if (isTemplateRefKey(refs, key)) {
+            return false
+          }
+
           return hasOwn(rawSetupState, key)
         }
 
-  const canSetRef = (ref: VNodeRef) => {
-    return !__DEV__ || !knownTemplateRefs.has(ref as any)
+  const canSetRef = (ref: VNodeRef, key?: string) => {
+    if (__DEV__ && knownTemplateRefs.has(ref as any)) {
+      return false
+    }
+    if (key && isTemplateRefKey(refs, key)) {
+      return false
+    }
+    return true
   }
 
   // dynamic ref changed. unset old ref
@@ -114,12 +126,11 @@ export function setRef(
         setupState[oldRef] = null
       }
     } else if (isRef(oldRef)) {
-      if (canSetRef(oldRef)) {
-        oldRef.value = null
-      }
-
       // this type assertion is valid since `oldRef` has already been asserted to be non-null
       const oldRawRefAtom = oldRawRef as VNodeNormalizedRefAtom
+      if (canSetRef(oldRef, oldRawRefAtom.k)) {
+        oldRef.value = null
+      }
       if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null
     }
   }
@@ -151,7 +162,7 @@ export function setRef(
                 }
               } else {
                 const newVal = [refValue]
-                if (canSetRef(ref)) {
+                if (canSetRef(ref, rawRef.k)) {
                   ref.value = newVal
                 }
                 if (rawRef.k) refs[rawRef.k] = newVal
@@ -166,7 +177,7 @@ export function setRef(
             setupState[ref] = value
           }
         } else if (_isRef) {
-          if (canSetRef(ref)) {
+          if (canSetRef(ref, rawRef.k)) {
             ref.value = value
           }
           if (rawRef.k) refs[rawRef.k] = value