瀏覽代碼

fix(useTemplateRef): properly fix readonly warning in dev and ensure prod behavior consistency

close #11808
close #11816
close #11810
Evan You 1 年之前
父節點
當前提交
9b7797d0d1

+ 25 - 3
packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts

@@ -1,4 +1,5 @@
 import {
+  type ShallowRef,
   h,
   nextTick,
   nodeOps,
@@ -84,12 +85,12 @@ describe('useTemplateRef', () => {
   })
 
   // #11795
-  test('should work when variable name is same as key', () => {
-    let tRef
+  test('should not attempt to set when variable name is same as key', () => {
+    let tRef: ShallowRef
     const key = 'refKey'
     const Comp = {
       setup() {
-        tRef = useTemplateRef(key)
+        tRef = useTemplateRef('_')
         return {
           [key]: tRef,
         }
@@ -102,5 +103,26 @@ describe('useTemplateRef', () => {
     render(h(Comp), root)
 
     expect('target is readonly').not.toHaveBeenWarned()
+    expect(tRef!.value).toBe(null)
+  })
+
+  test('should work when used as direct ref value (compiled in prod mode)', () => {
+    __DEV__ = false
+    try {
+      let foo: ShallowRef
+      const Comp = {
+        setup() {
+          foo = useTemplateRef('foo')
+          return () => h('div', { ref: foo })
+        },
+      }
+      const root = nodeOps.createElement('div')
+      render(h(Comp), root)
+
+      expect('target is readonly').not.toHaveBeenWarned()
+      expect(foo!.value).toBe(root.children[0])
+    } finally {
+      __DEV__ = true
+    }
   })
 })

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

@@ -3,6 +3,8 @@ import { getCurrentInstance } from '../component'
 import { warn } from '../warning'
 import { EMPTY_OBJ } from '@vue/shared'
 
+export const knownTemplateRefs: WeakSet<ShallowRef> = new WeakSet()
+
 export function useTemplateRef<T = unknown, Keys extends string = string>(
   key: Keys,
 ): Readonly<ShallowRef<T | null>> {
@@ -10,7 +12,6 @@ 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__ &&
@@ -31,5 +32,9 @@ export function useTemplateRef<T = unknown, Keys extends string = string>(
         `instance to be associated with.`,
     )
   }
-  return (__DEV__ ? readonly(r) : r) as any
+  const ret = __DEV__ ? readonly(r) : r
+  if (__DEV__) {
+    knownTemplateRefs.add(ret)
+  }
+  return ret
 }

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

@@ -11,11 +11,12 @@ import {
 } from '@vue/shared'
 import { isAsyncWrapper } from './apiAsyncComponent'
 import { warn } from './warning'
-import { isRef } from '@vue/reactivity'
+import { isRef, toRaw } from '@vue/reactivity'
 import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import type { SchedulerJob } from './scheduler'
 import { queuePostRenderEffect } from './renderer'
 import { getComponentPublicInstance } from './component'
+import { knownTemplateRefs } from './helpers/useTemplateRef'
 
 /**
  * Function for handling a template ref
@@ -63,12 +64,16 @@ export function setRef(
   const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
   const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
   const setupState = owner.setupState
+  const rawSetupState = toRaw(setupState)
   const canSetSetupRef =
     setupState === EMPTY_OBJ
       ? () => false
-      : (key: string) =>
-          hasOwn(setupState, key) &&
-          !(Object.getOwnPropertyDescriptor(refs, key) || EMPTY_OBJ).get
+      : (key: string) => {
+          if (__DEV__ && knownTemplateRefs.has(rawSetupState[key] as any)) {
+            return false
+          }
+          return hasOwn(rawSetupState, key)
+        }
 
   // dynamic ref changed. unset old ref
   if (oldRef != null && oldRef !== ref) {