mirror of https://github.com/vuejs/core.git
Merge f826689846
into ba391f5fdf
This commit is contained in:
commit
d6eb901616
|
@ -10,6 +10,7 @@ import {
|
||||||
render,
|
render,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
watch,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
|
|
||||||
describe('api: template refs', () => {
|
describe('api: template refs', () => {
|
||||||
|
@ -179,6 +180,89 @@ describe('api: template refs', () => {
|
||||||
expect(el.value).toBe(null)
|
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 () => {
|
it('unset old ref when new ref is absent', async () => {
|
||||||
const root1 = nodeOps.createElement('div')
|
const root1 = nodeOps.createElement('div')
|
||||||
const root2 = nodeOps.createElement('div')
|
const root2 = nodeOps.createElement('div')
|
||||||
|
|
|
@ -13,11 +13,12 @@ import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isRef, toRaw } from '@vue/reactivity'
|
import { isRef, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import type { SchedulerJob } from './scheduler'
|
import { type SchedulerJob, SchedulerJobFlags } from './scheduler'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
||||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||||
|
|
||||||
|
const pendingSetRefMap = new WeakMap<VNodeNormalizedRef, SchedulerJob>()
|
||||||
/**
|
/**
|
||||||
* Function for handling a template ref
|
* Function for handling a template ref
|
||||||
*/
|
*/
|
||||||
|
@ -96,6 +97,7 @@ export function setRef(
|
||||||
|
|
||||||
// dynamic ref changed. unset old ref
|
// dynamic ref changed. unset old ref
|
||||||
if (oldRef != null && oldRef !== ref) {
|
if (oldRef != null && oldRef !== ref) {
|
||||||
|
invalidatePendingSetRef(oldRawRef!)
|
||||||
if (isString(oldRef)) {
|
if (isString(oldRef)) {
|
||||||
refs[oldRef] = null
|
refs[oldRef] = null
|
||||||
if (canSetSetupRef(oldRef)) {
|
if (canSetSetupRef(oldRef)) {
|
||||||
|
@ -153,9 +155,15 @@ export function setRef(
|
||||||
// #1789: for non-null values, set them after render
|
// #1789: for non-null values, set them after render
|
||||||
// null values means this is unmount and it should not overwrite another
|
// null values means this is unmount and it should not overwrite another
|
||||||
// ref with the same key
|
// ref with the same key
|
||||||
;(doSet as SchedulerJob).id = -1
|
const job: SchedulerJob = () => {
|
||||||
queuePostRenderEffect(doSet, parentSuspense)
|
doSet()
|
||||||
|
pendingSetRefMap.delete(rawRef)
|
||||||
|
}
|
||||||
|
job.id = -1
|
||||||
|
pendingSetRefMap.set(rawRef, job)
|
||||||
|
queuePostRenderEffect(job, parentSuspense)
|
||||||
} else {
|
} else {
|
||||||
|
invalidatePendingSetRef(rawRef)
|
||||||
doSet()
|
doSet()
|
||||||
}
|
}
|
||||||
} else if (__DEV__) {
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue