瀏覽代碼

feat(templateRef): avoid setting direct ref of useTemplateRef in dev (#14442)

sync https://github.com/vuejs/core/pull/13449/changes
edison 2 月之前
父節點
當前提交
78eb86ccd2

+ 5 - 0
packages/runtime-core/src/index.ts

@@ -683,3 +683,8 @@ export {
   warnExtraneousAttributes,
   getFunctionalFallthrough,
 } from './componentRenderUtils'
+
+/**
+ * @internal
+ */
+export { knownTemplateRefs } from './helpers/useTemplateRef'

+ 124 - 0
packages/runtime-vapor/__tests__/dom/templateRef.spec.ts

@@ -814,6 +814,130 @@ describe('api: template ref', () => {
     expect(el2.value).toBe(null)
   })
 
+  test('should work when used with direct ref value with ref_key', () => {
+    let tRef: ShallowRef
+    const key = 'refKey'
+    const { host } = define({
+      setup() {
+        tRef = useTemplateRef(key)
+      },
+      render() {
+        const n0 = template('<div></div>')() as Element
+        createTemplateRefSetter()(n0, tRef!, false, key)
+        return n0
+      },
+    }).render()
+
+    expect('target is readonly').not.toHaveBeenWarned()
+    expect(tRef!.value).toBe(host.children[0])
+  })
+
+  test('should work when used with direct ref value with ref_key and ref_for', () => {
+    let tRef: ShallowRef
+    const key = 'refKey'
+    define({
+      setup() {
+        tRef = useTemplateRef(key)
+      },
+      render() {
+        const n0 = template('<div></div>')() as Element
+        const n1 = createFor(
+          () => [1, 2, 3],
+          x => {
+            const n2 = template('<span></span>')() as Element
+            createTemplateRefSetter()(n2, tRef!, true, key)
+            setElementText(n2, x)
+            return n2
+          },
+        )
+        insert(n1, n0 as ParentNode)
+        return n0
+      },
+    }).render()
+
+    expect('target is readonly').not.toHaveBeenWarned()
+    expect(tRef!.value).toHaveLength(3)
+  })
+
+  test('should not work when used with direct ref value without ref_key (in dev mode)', () => {
+    let tRef: ShallowRef
+    define({
+      setup() {
+        tRef = useTemplateRef('refKey')
+      },
+      render() {
+        const n0 = template('<div></div>')() as Element
+        createTemplateRefSetter()(n0, tRef!)
+        return n0
+      },
+    }).render()
+
+    expect(tRef!.value).toBeNull()
+  })
+
+  test('should work when used as direct ref value with ref_key and ref_for (compiled in prod mode)', () => {
+    __DEV__ = false
+    try {
+      let tRef: ShallowRef
+      const key = 'refKey'
+      define({
+        setup() {
+          tRef = useTemplateRef(key)
+        },
+        render() {
+          const n0 = template('<div></div>')() as Element
+          const n1 = createFor(
+            () => [1, 2, 3],
+            x => {
+              const n2 = template('<span></span>')() as Element
+              createTemplateRefSetter()(n2, tRef!, true, key)
+              setElementText(n2, x)
+              return n2
+            },
+          )
+          insert(n1, n0 as ParentNode)
+          return n0
+        },
+      }).render()
+
+      expect('target is readonly').not.toHaveBeenWarned()
+      expect(tRef!.value).toHaveLength(3)
+    } finally {
+      __DEV__ = true
+    }
+  })
+
+  test('should work when used as direct ref value with ref_for but without ref_key (compiled in prod mode)', () => {
+    __DEV__ = false
+    try {
+      let tRef: ShallowRef
+      define({
+        setup() {
+          tRef = useTemplateRef('refKey')
+        },
+        render() {
+          const n0 = template('<div></div>')() as Element
+          const n1 = createFor(
+            () => [1, 2, 3],
+            x => {
+              const n2 = template('<span></span>')() as Element
+              createTemplateRefSetter()(n2, tRef!, true)
+              setElementText(n2, x)
+              return n2
+            },
+          )
+          insert(n1, n0 as ParentNode)
+          return n0
+        },
+      }).render()
+
+      expect('target is readonly').not.toHaveBeenWarned()
+      expect(tRef!.value).toHaveLength(3)
+    } finally {
+      __DEV__ = true
+    }
+  })
+
   // TODO: can not reproduce in Vapor
   // // #2078
   // test('handling multiple merged refs', async () => {

+ 13 - 5
packages/runtime-vapor/src/apiTemplateRef.ts

@@ -11,6 +11,7 @@ import {
   callWithErrorHandling,
   createCanSetSetupRefChecker,
   isAsyncWrapper,
+  knownTemplateRefs,
   queuePostFlushCb,
   warn,
 } from '@vue/runtime-dom'
@@ -102,6 +103,11 @@ export function setRef(
   const canSetSetupRef = __DEV__
     ? createCanSetSetupRefChecker(setupState, refs)
     : NO
+
+  const canSetRef = (ref: NodeRef) => {
+    return !__DEV__ || !knownTemplateRefs.has(ref as any)
+  }
+
   // dynamic ref changed. unset old ref
   if (oldRef != null && oldRef !== ref) {
     if (isString(oldRef)) {
@@ -110,7 +116,7 @@ export function setRef(
         setupState[oldRef] = null
       }
     } else if (isRef(oldRef)) {
-      oldRef.value = null
+      if (canSetRef(oldRef)) oldRef.value = null
     }
   }
 
@@ -136,7 +142,9 @@ export function setRef(
             ? __DEV__ && canSetSetupRef(ref)
               ? setupState[ref]
               : refs[ref]
-            : ref.value
+            : canSetRef(ref) || !refKey
+              ? ref.value
+              : refs[refKey]
 
           if (!isArray(existing)) {
             existing = [refValue]
@@ -150,7 +158,7 @@ export function setRef(
                 existing = setupState[ref]
               }
             } else {
-              ref.value = existing
+              if (canSetRef(ref)) ref.value = existing
               if (refKey) refs[refKey] = existing
             }
           } else if (!existing.includes(refValue)) {
@@ -162,7 +170,7 @@ export function setRef(
             setupState[ref] = refValue
           }
         } else if (_isRef) {
-          ref.value = refValue
+          if (canSetRef(ref)) ref.value = refValue
           if (refKey) refs[refKey] = refValue
         } else if (__DEV__) {
           warn('Invalid template ref type:', ref, `(${typeof ref})`)
@@ -180,7 +188,7 @@ export function setRef(
               setupState[ref] = null
             }
           } else if (_isRef) {
-            ref.value = null
+            if (canSetRef(ref)) ref.value = null
             if (refKey) refs[refKey] = null
           }
         })