From ca052b804e8af250d0e9e6c7c7cc860886f8de65 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 3 Jan 2025 11:10:04 +0800 Subject: [PATCH 1/5] fix(templateRef): prevent unnecessary set ref during unmounting --- packages/runtime-core/src/rendererTemplateRef.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index ca21030dc35..5c3ab19db0a 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -153,9 +153,13 @@ export function setRef( // #1789: for non-null values, set them after render // null values means this is unmount and it should not overwrite another // ref with the same key - ;(doSet as SchedulerJob).id = -1 - queuePostRenderEffect(doSet, parentSuspense) + const job: SchedulerJob = () => { + if (!(vnode as any).__isUnmounting) doSet() + } + job.id = -1 + queuePostRenderEffect(job, parentSuspense) } else { + ;(vnode as any).__isUnmounting = true doSet() } } else if (__DEV__) { From 864273294f40153343c5bf224d878b0f2bc635af Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 3 Jan 2025 11:21:21 +0800 Subject: [PATCH 2/5] chore: update --- packages/runtime-core/src/rendererTemplateRef.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index 5c3ab19db0a..062eaacae0e 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -13,11 +13,12 @@ import { isAsyncWrapper } from './apiAsyncComponent' import { warn } from './warning' import { isRef, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' -import type { SchedulerJob } from './scheduler' +import { type SchedulerJob, SchedulerJobFlags } from './scheduler' import { queuePostRenderEffect } from './renderer' import { type ComponentOptions, getComponentPublicInstance } from './component' import { knownTemplateRefs } from './helpers/useTemplateRef' +const pendingSetRef = new WeakMap() /** * Function for handling a template ref */ @@ -153,13 +154,12 @@ export function setRef( // #1789: for non-null values, set them after render // null values means this is unmount and it should not overwrite another // ref with the same key - const job: SchedulerJob = () => { - if (!(vnode as any).__isUnmounting) doSet() - } - job.id = -1 - queuePostRenderEffect(job, parentSuspense) + ;(doSet as SchedulerJob).id = -1 + pendingSetRef.set(vnode, doSet) + queuePostRenderEffect(doSet, parentSuspense) } else { - ;(vnode as any).__isUnmounting = true + const pendingSet = pendingSetRef.get(vnode) + if (pendingSet) pendingSet.flags! |= SchedulerJobFlags.DISPOSED doSet() } } else if (__DEV__) { From 9403228ed1bd72e4267cd618ec39b7c54c9c4457 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 3 Jan 2025 11:29:51 +0800 Subject: [PATCH 3/5] Revert "chore: update" This reverts commit 864273294f40153343c5bf224d878b0f2bc635af. --- packages/runtime-core/src/rendererTemplateRef.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index 062eaacae0e..5c3ab19db0a 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -13,12 +13,11 @@ import { isAsyncWrapper } from './apiAsyncComponent' import { warn } from './warning' import { isRef, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' -import { type SchedulerJob, SchedulerJobFlags } from './scheduler' +import type { SchedulerJob } from './scheduler' import { queuePostRenderEffect } from './renderer' import { type ComponentOptions, getComponentPublicInstance } from './component' import { knownTemplateRefs } from './helpers/useTemplateRef' -const pendingSetRef = new WeakMap() /** * Function for handling a template ref */ @@ -154,12 +153,13 @@ export function setRef( // #1789: for non-null values, set them after render // null values means this is unmount and it should not overwrite another // ref with the same key - ;(doSet as SchedulerJob).id = -1 - pendingSetRef.set(vnode, doSet) - queuePostRenderEffect(doSet, parentSuspense) + const job: SchedulerJob = () => { + if (!(vnode as any).__isUnmounting) doSet() + } + job.id = -1 + queuePostRenderEffect(job, parentSuspense) } else { - const pendingSet = pendingSetRef.get(vnode) - if (pendingSet) pendingSet.flags! |= SchedulerJobFlags.DISPOSED + ;(vnode as any).__isUnmounting = true doSet() } } else if (__DEV__) { From 79ad0d27ef3a06610936ab902e61e699794e4ed5 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 3 Jan 2025 14:19:49 +0800 Subject: [PATCH 4/5] test: add test case --- .../__tests__/rendererTemplateRef.spec.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts index a7ae7a06bfd..ae30878a4fc 100644 --- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts @@ -10,6 +10,7 @@ import { render, serializeInner, shallowRef, + watch, } from '@vue/runtime-test' describe('api: template refs', () => { @@ -179,6 +180,51 @@ describe('api: template refs', () => { expect(el.value).toBe(null) }) + // #12639 + it('update and unmount child in the same tick', async () => { + const root = nodeOps.createElement('div') + const el = ref(null) + const toggle = ref(true) + const show = ref(true) + + const Comp = defineComponent({ + emits: ['change'], + props: ['show'], + setup(props, { emit }) { + watch( + () => props.show, + () => { + emit('change') + }, + ) + return () => h('div', 'hi') + }, + }) + + const App = { + setup() { + return { + refKey: el, + } + }, + render() { + return toggle.value + ? h(Comp, { + ref: 'refKey', + show: show.value, + onChange: () => (toggle.value = false), + }) + : null + }, + } + render(h(App), root) + expect(el.value).not.toBe(null) + + show.value = false + await nextTick() + expect(el.value).toBe(null) + }) + test('string ref inside slots', async () => { const root = nodeOps.createElement('div') const spy = vi.fn() From 10e180989c2f6de79470c1f61497cfc625d5c778 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 3 Jan 2025 14:34:08 +0800 Subject: [PATCH 5/5] chore: update --- packages/runtime-core/src/rendererTemplateRef.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index 5c3ab19db0a..0e40c9534ca 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -13,11 +13,12 @@ import { isAsyncWrapper } from './apiAsyncComponent' import { warn } from './warning' import { isRef, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' -import type { SchedulerJob } from './scheduler' +import { type SchedulerJob, SchedulerJobFlags } from './scheduler' import { queuePostRenderEffect } from './renderer' import { type ComponentOptions, getComponentPublicInstance } from './component' import { knownTemplateRefs } from './helpers/useTemplateRef' +const pendingSetRefMap = new WeakMap() /** * Function for handling a template ref */ @@ -154,12 +155,18 @@ export function setRef( // null values means this is unmount and it should not overwrite another // ref with the same key const job: SchedulerJob = () => { - if (!(vnode as any).__isUnmounting) doSet() + doSet() + pendingSetRefMap.delete(vnode) } job.id = -1 + pendingSetRefMap.set(vnode, job) queuePostRenderEffect(job, parentSuspense) } else { - ;(vnode as any).__isUnmounting = true + const pendingSetRef = pendingSetRefMap.get(vnode) + if (pendingSetRef) { + pendingSetRef.flags! |= SchedulerJobFlags.DISPOSED + pendingSetRefMap.delete(vnode) + } doSet() } } else if (__DEV__) {