Skip to content

Commit

Permalink
fix(watch): should not fire pre watcher on child component unmount (#…
Browse files Browse the repository at this point in the history
…7181)

close #7030
  • Loading branch information
rudyxu1102 committed Dec 8, 2023
1 parent cdac121 commit 6784f0b
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 1 deletion.
92 changes: 92 additions & 0 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,98 @@ describe('api: watch', () => {
expect(cb).not.toHaveBeenCalled()
})

// #7030
it('should not fire on child component unmount w/ flush: pre', async () => {
const visible = ref(true)
const cb = vi.fn()
const Parent = defineComponent({
props: ['visible'],
render() {
return visible.value ? h(Comp) : null
}
})
const Comp = {
setup() {
watch(visible, cb, { flush: 'pre' })
},
render() {}
}
const App = {
render() {
return h(Parent, {
visible: visible.value
})
}
}
render(h(App), nodeOps.createElement('div'))
expect(cb).not.toHaveBeenCalled()
visible.value = false
await nextTick()
expect(cb).not.toHaveBeenCalled()
})

// #7030
it('flush: pre watcher in child component should not fire before parent update', async () => {
const b = ref(0)
const calls: string[] = []

const Comp = {
setup() {
watch(
() => b.value,
val => {
calls.push('watcher child')
},
{ flush: 'pre' }
)
return () => {
b.value
calls.push('render child')
}
}
}

const Parent = {
props: ['a'],
setup() {
watch(
() => b.value,
val => {
calls.push('watcher parent')
},
{ flush: 'pre' }
)
return () => {
b.value
calls.push('render parent')
return h(Comp)
}
}
}

const App = {
render() {
return h(Parent, {
a: b.value
})
}
}

render(h(App), nodeOps.createElement('div'))
expect(calls).toEqual(['render parent', 'render child'])

b.value++
await nextTick()
expect(calls).toEqual([
'render parent',
'render child',
'watcher parent',
'render parent',
'watcher child',
'render child'
])
})

// #1763
it('flush: pre watcher watching props should fire before child update', async () => {
const a = ref(0)
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,7 @@ function baseCreateRenderer(
pauseTracking()
// props update may have triggered pre-flush watchers.
// flush them before the render update.
flushPreFlushCbs()
flushPreFlushCbs(instance)
resetTracking()
}

Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-core/src/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function queuePostFlushCb(cb: SchedulerJobs) {
}

export function flushPreFlushCbs(
instance?: ComponentInternalInstance,
seen?: CountMap,
// if currently flushing, skip the current job itself
i = isFlushing ? flushIndex + 1 : 0
Expand All @@ -149,6 +150,9 @@ export function flushPreFlushCbs(
for (; i < queue.length; i++) {
const cb = queue[i]
if (cb && cb.pre) {
if (instance && cb.id !== instance.uid) {
continue
}
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
continue
}
Expand Down

0 comments on commit 6784f0b

Please sign in to comment.