Parcourir la source

fix(runtime-vapor): cleanup stale refs when dynamic vdom component ref target changes (#14501)

edison il y a 1 mois
Parent
commit
178b77f41b

+ 42 - 2
packages/runtime-vapor/__tests__/vdomInterop.spec.ts

@@ -736,6 +736,7 @@ describe('vdomInterop', () => {
     })
 
     it('dynamic component includes vdom component', async () => {
+      const vdomRef = ref<any>(null)
       const VdomChild = defineComponent({
         setup(_, { expose }) {
           expose({ name: 'vdomChild' })
@@ -755,8 +756,6 @@ describe('vdomInterop', () => {
         },
       })
 
-      const vdomRef = ref<any>(null)
-
       define({
         setup() {
           return () => h(VaporChild as any)
@@ -767,6 +766,47 @@ describe('vdomInterop', () => {
       expect(vdomRef.value).toBeDefined()
       expect(vdomRef.value.name).toBe('vdomChild')
     })
+
+    it('dynamic component includes vdom component should cleanup old ref', async () => {
+      const VdomChild = defineComponent({
+        setup(_, { expose }) {
+          expose({ name: 'vdomChild' })
+          return () => h('div', 'vdom child')
+        },
+      })
+
+      const useA = ref(true)
+      const refA = ref<any>(null)
+      const refB = ref<any>(null)
+
+      const VaporChild = defineVaporComponent({
+        setup() {
+          const setRef = createTemplateRefSetter()
+          const n0 = createDynamicComponent(() => VdomChild)
+          renderEffect(() => {
+            setRef(n0, useA.value ? refA : refB, false, 'vdomRef')
+          })
+          return n0
+        },
+      })
+
+      define({
+        setup() {
+          return () => h(VaporChild as any)
+        },
+      }).render()
+
+      await nextTick()
+      expect(refA.value).toBeDefined()
+      expect(refA.value.name).toBe('vdomChild')
+      expect(refB.value).toBe(null)
+
+      useA.value = false
+      await nextTick()
+      expect(refA.value).toBe(null)
+      expect(refB.value).toBeDefined()
+      expect(refB.value.name).toBe('vdomChild')
+    })
   })
 
   describe('dynamic component', () => {

+ 15 - 11
packages/runtime-vapor/src/apiTemplateRef.ts

@@ -106,16 +106,7 @@ function setRef(
 ): NodeRef | undefined {
   if (!instance || instance.isUnmounted) return
 
-  // vdom interop
-  const handleVdomRef = (target: any): boolean => {
-    if (isInteropEnabled && isFragment(target) && target.setRef) {
-      target.setRef(instance, ref, refFor, refKey)
-      return true
-    }
-    return false
-  }
-  if (handleVdomRef(el)) return ref
-
+  // async component
   if (isVaporComponent(el) && isAsyncWrapper(el)) {
     // unresolved: handled in DynamicFragment's updated hook
     if (!el.type.__asyncResolved) return
@@ -126,7 +117,20 @@ function setRef(
 
   const setupState: any = __DEV__ ? instance.setupState || {} : null
   const refValue = getRefValue(el)
-  if (handleVdomRef(refValue)) return ref
+
+  // vdom interop
+  if (isInteropEnabled) {
+    const target =
+      isFragment(el) && el.setRef
+        ? el
+        : refValue && isFragment(refValue) && refValue.setRef
+          ? refValue
+          : null
+    if (target) {
+      target.setRef!(instance, ref, refFor, refKey)
+      return ref
+    }
+  }
 
   const refs =
     instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs

+ 7 - 2
packages/runtime-vapor/src/vdomInterop.ts

@@ -626,6 +626,7 @@ function createVDOMComponent(
     refFor: boolean,
     refKey: string | undefined,
   ): void => {
+    const oldRawRef = rawRef
     rawRef = normalizeRef(
       {
         ref: ref as any,
@@ -635,8 +636,12 @@ function createVDOMComponent(
       instance as any,
     )
 
-    if (isMounted && rawRef) {
-      vdomSetRef(rawRef, null, null, vnode)
+    if (isMounted) {
+      if (rawRef) {
+        vdomSetRef(rawRef, oldRawRef, null, vnode)
+      } else if (oldRawRef) {
+        vdomSetRef(oldRawRef, null, null, vnode, true)
+      }
     }
   }