render,
serializeInner,
shallowRef,
+ watch,
} from '@vue/runtime-test'
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')
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
*/
// dynamic ref changed. unset old ref
if (oldRef != null && oldRef !== ref) {
+ invalidatePendingSetRef(oldRawRef!)
if (isString(oldRef)) {
refs[oldRef] = null
if (canSetSetupRef(oldRef)) {
// #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__) {
}
}
}
+
+function invalidatePendingSetRef(rawRef: VNodeNormalizedRef) {
+ const pendingSetRef = pendingSetRefMap.get(rawRef)
+ if (pendingSetRef) {
+ pendingSetRef.flags! |= SchedulerJobFlags.DISPOSED
+ pendingSetRefMap.delete(rawRef)
+ }
+}