Skip to content

Commit caccec3

Browse files
authored
fix(runtime-core/scheduler): sort postFlushCbs to ensure refs are set before lifecycle hooks (#1854)
fix #1852
1 parent 3fa5c9f commit caccec3

File tree

4 files changed

+76
-18
lines changed

4 files changed

+76
-18
lines changed

packages/runtime-core/__tests__/apiWatch.spec.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ref,
88
h
99
} from '../src/index'
10-
import { render, nodeOps, serializeInner } from '@vue/runtime-test'
10+
import { render, nodeOps, serializeInner, TestElement } from '@vue/runtime-test'
1111
import {
1212
ITERATE_KEY,
1313
DebuggerEvent,
@@ -484,6 +484,37 @@ describe('api: watch', () => {
484484
expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
485485
})
486486

487+
// #1852
488+
it('flush: post watcher should fire after template refs updated', async () => {
489+
const toggle = ref(false)
490+
let dom: TestElement | null = null
491+
492+
const App = {
493+
setup() {
494+
const domRef = ref<TestElement | null>(null)
495+
496+
watch(
497+
toggle,
498+
() => {
499+
dom = domRef.value
500+
},
501+
{ flush: 'post' }
502+
)
503+
504+
return () => {
505+
return toggle.value ? h('p', { ref: domRef }) : null
506+
}
507+
}
508+
}
509+
510+
render(h(App), nodeOps.createElement('div'))
511+
expect(dom).toBe(null)
512+
513+
toggle.value = true
514+
await nextTick()
515+
expect(dom!.tag).toBe('p')
516+
})
517+
487518
it('deep', async () => {
488519
const state = reactive({
489520
nested: {

packages/runtime-core/__tests__/scheduler.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,22 @@ describe('scheduler', () => {
403403
expect(calls).toEqual(['job3', 'job2', 'job1'])
404404
})
405405

406+
test('sort SchedulerCbs based on id', async () => {
407+
const calls: string[] = []
408+
const cb1 = () => calls.push('cb1')
409+
// cb1 has no id
410+
const cb2 = () => calls.push('cb2')
411+
cb2.id = 2
412+
const cb3 = () => calls.push('cb3')
413+
cb3.id = 1
414+
415+
queuePostFlushCb(cb1)
416+
queuePostFlushCb(cb2)
417+
queuePostFlushCb(cb3)
418+
await nextTick()
419+
expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
420+
})
421+
406422
// #1595
407423
test('avoid duplicate postFlushCb invocation', async () => {
408424
const calls: string[] = []

packages/runtime-core/src/renderer.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ import {
4242
flushPostFlushCbs,
4343
invalidateJob,
4444
flushPreFlushCbs,
45-
SchedulerJob
45+
SchedulerJob,
46+
SchedulerCb
4647
} from './scheduler'
4748
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
4849
import { updateProps } from './componentProps'
@@ -330,17 +331,20 @@ export const setRef = (
330331
// null values means this is unmount and it should not overwrite another
331332
// ref with the same key
332333
if (value) {
334+
;(doSet as SchedulerCb).id = -1
333335
queuePostRenderEffect(doSet, parentSuspense)
334336
} else {
335337
doSet()
336338
}
337339
} else if (isRef(ref)) {
340+
const doSet = () => {
341+
ref.value = value
342+
}
338343
if (value) {
339-
queuePostRenderEffect(() => {
340-
ref.value = value
341-
}, parentSuspense)
344+
;(doSet as SchedulerCb).id = -1
345+
queuePostRenderEffect(doSet, parentSuspense)
342346
} else {
343-
ref.value = value
347+
doSet()
344348
}
345349
} else if (isFunction(ref)) {
346350
callWithErrorHandling(ref, parentComponent, ErrorCodes.FUNCTION_REF, [

packages/runtime-core/src/scheduler.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,21 @@ export interface SchedulerJob {
2222
allowRecurse?: boolean
2323
}
2424

25+
export type SchedulerCb = Function & { id?: number }
26+
export type SchedulerCbs = SchedulerCb | SchedulerCb[]
27+
2528
let isFlushing = false
2629
let isFlushPending = false
2730

2831
const queue: (SchedulerJob | null)[] = []
2932
let flushIndex = 0
3033

31-
const pendingPreFlushCbs: Function[] = []
32-
let activePreFlushCbs: Function[] | null = null
34+
const pendingPreFlushCbs: SchedulerCb[] = []
35+
let activePreFlushCbs: SchedulerCb[] | null = null
3336
let preFlushIndex = 0
3437

35-
const pendingPostFlushCbs: Function[] = []
36-
let activePostFlushCbs: Function[] | null = null
38+
const pendingPostFlushCbs: SchedulerCb[] = []
39+
let activePostFlushCbs: SchedulerCb[] | null = null
3740
let postFlushIndex = 0
3841

3942
const resolvedPromise: Promise<any> = Promise.resolve()
@@ -42,7 +45,7 @@ let currentFlushPromise: Promise<void> | null = null
4245
let currentPreFlushParentJob: SchedulerJob | null = null
4346

4447
const RECURSION_LIMIT = 100
45-
type CountMap = Map<SchedulerJob | Function, number>
48+
type CountMap = Map<SchedulerJob | SchedulerCb, number>
4649

4750
export function nextTick(fn?: () => void): Promise<void> {
4851
const p = currentFlushPromise || resolvedPromise
@@ -84,9 +87,9 @@ export function invalidateJob(job: SchedulerJob) {
8487
}
8588

8689
function queueCb(
87-
cb: Function | Function[],
88-
activeQueue: Function[] | null,
89-
pendingQueue: Function[],
90+
cb: SchedulerCbs,
91+
activeQueue: SchedulerCb[] | null,
92+
pendingQueue: SchedulerCb[],
9093
index: number
9194
) {
9295
if (!isArray(cb)) {
@@ -108,11 +111,11 @@ function queueCb(
108111
queueFlush()
109112
}
110113

111-
export function queuePreFlushCb(cb: Function) {
114+
export function queuePreFlushCb(cb: SchedulerCb) {
112115
queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)
113116
}
114117

115-
export function queuePostFlushCb(cb: Function | Function[]) {
118+
export function queuePostFlushCb(cb: SchedulerCbs) {
116119
queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex)
117120
}
118121

@@ -152,6 +155,9 @@ export function flushPostFlushCbs(seen?: CountMap) {
152155
if (__DEV__) {
153156
seen = seen || new Map()
154157
}
158+
159+
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
160+
155161
for (
156162
postFlushIndex = 0;
157163
postFlushIndex < activePostFlushCbs.length;
@@ -167,7 +173,8 @@ export function flushPostFlushCbs(seen?: CountMap) {
167173
}
168174
}
169175

170-
const getId = (job: SchedulerJob) => (job.id == null ? Infinity : job.id)
176+
const getId = (job: SchedulerJob | SchedulerCb) =>
177+
job.id == null ? Infinity : job.id
171178

172179
function flushJobs(seen?: CountMap) {
173180
isFlushPending = false
@@ -215,7 +222,7 @@ function flushJobs(seen?: CountMap) {
215222
}
216223
}
217224

218-
function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob | Function) {
225+
function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob | SchedulerCb) {
219226
if (!seen.has(fn)) {
220227
seen.set(fn, 1)
221228
} else {

0 commit comments

Comments
 (0)