From 3b6f377eb441cb94e5bb7cd5e91a41dace2b4f73 Mon Sep 17 00:00:00 2001 From: Map1en Date: Thu, 13 Nov 2025 23:46:09 +0900 Subject: [PATCH] fix(components): [popper] trigger memory leak issue (#22763) * fix(components): trigger memory leak issue * fix: update test * Update packages/components/popper/src/trigger.vue --------- Co-authored-by: btea <2356281422@qq.com> --- .../popper/__tests__/trigger.test.tsx | 25 +++++++++++++++++++ packages/components/popper/src/trigger.vue | 18 +++++++++---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/components/popper/__tests__/trigger.test.tsx b/packages/components/popper/__tests__/trigger.test.tsx index 2911754fdf..a5d6824c8b 100644 --- a/packages/components/popper/__tests__/trigger.test.tsx +++ b/packages/components/popper/__tests__/trigger.test.tsx @@ -73,5 +73,30 @@ describe('', () => { await nextTick() expect(onClick).toHaveBeenCalled() }) + it('should cleanup listeners when triggerRef changes', async () => { + const onClick = vi.fn() + const first = document.createElement('p') + const removeSpy = vi.spyOn(first, 'removeEventListener') + const addSpy = vi.spyOn(first, 'addEventListener') + + wrapper = mountTrigger({ + onClick, + virtualTriggering: true, + virtualRef: first, + }) + await nextTick() + + await wrapper.setProps({ + virtualRef: { + getBoundingClientRect: () => { + return { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 } + }, + }, + }) + await nextTick() + + expect(addSpy).toHaveBeenCalledWith('click', onClick, false) + expect(removeSpy).toHaveBeenCalledWith('click', onClick, false) + }) }) }) diff --git a/packages/components/popper/src/trigger.vue b/packages/components/popper/src/trigger.vue index a02b92e725..dfd6bc0e6c 100644 --- a/packages/components/popper/src/trigger.vue +++ b/packages/components/popper/src/trigger.vue @@ -86,6 +86,19 @@ onMounted(() => { (el, prevEl) => { virtualTriggerAriaStopWatch?.() virtualTriggerAriaStopWatch = undefined + + if (isElement(prevEl)) { + TRIGGER_ELE_EVENTS.forEach((eventName) => { + const handler = props[eventName] + if (handler) { + ;(prevEl as HTMLElement).removeEventListener( + eventName.slice(2).toLowerCase(), + handler, + ['onFocus', 'onBlur'].includes(eventName) + ) + } + }) + } if (isElement(el)) { TRIGGER_ELE_EVENTS.forEach((eventName) => { const handler = props[eventName] @@ -95,11 +108,6 @@ onMounted(() => { handler, ['onFocus', 'onBlur'].includes(eventName) ) - ;(prevEl as HTMLElement)?.removeEventListener?.( - eventName.slice(2).toLowerCase(), - handler, - ['onFocus', 'onBlur'].includes(eventName) - ) } }) if (isFocusable(el as HTMLElement)) {