This commit is contained in:
edison 2025-07-01 11:03:51 +02:00 committed by GitHub
commit d6eb901616
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 103 additions and 3 deletions

View File

@ -10,6 +10,7 @@ import {
render,
serializeInner,
shallowRef,
watch,
} from '@vue/runtime-test'
describe('api: template refs', () => {
@ -179,6 +180,89 @@ 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)
})
it('set and change ref in the same tick', async () => {
const root = nodeOps.createElement('div')
const show = ref(false)
const refName = ref('a')
const Child = defineComponent({
setup() {
refName.value = 'b'
return () => {}
},
})
const Comp = {
render() {
return h(Child, {
ref: refName.value,
})
},
updated(this: any) {
expect(this.$refs.a).toBe(null)
expect(this.$refs.b).not.toBe(null)
},
}
const App = {
render() {
return show.value ? h(Comp) : null
},
}
render(h(App), root)
expect(refName.value).toBe('a')
show.value = true
await nextTick()
expect(refName.value).toBe('b')
})
it('unset old ref when new ref is absent', async () => {
const root1 = nodeOps.createElement('div')
const root2 = nodeOps.createElement('div')

View File

@ -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<VNodeNormalizedRef, SchedulerJob>()
/**
* Function for handling a template ref
*/
@ -96,6 +97,7 @@ export function setRef(
// dynamic ref changed. unset old ref
if (oldRef != null && oldRef !== ref) {
invalidatePendingSetRef(oldRawRef!)
if (isString(oldRef)) {
refs[oldRef] = null
if (canSetSetupRef(oldRef)) {
@ -153,9 +155,15 @@ 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 = () => {
doSet()
pendingSetRefMap.delete(rawRef)
}
job.id = -1
pendingSetRefMap.set(rawRef, job)
queuePostRenderEffect(job, parentSuspense)
} else {
invalidatePendingSetRef(rawRef)
doSet()
}
} else if (__DEV__) {
@ -163,3 +171,11 @@ export function setRef(
}
}
}
function invalidatePendingSetRef(rawRef: VNodeNormalizedRef) {
const pendingSetRef = pendingSetRefMap.get(rawRef)
if (pendingSetRef) {
pendingSetRef.flags! |= SchedulerJobFlags.DISPOSED
pendingSetRefMap.delete(rawRef)
}
}