Skip to content

Commit c2b7333

Browse files
feat: extend ReactiveEffect using derived class RenderEffect
1 parent 9bda252 commit c2b7333

File tree

6 files changed

+81
-76
lines changed

6 files changed

+81
-76
lines changed

packages/reactivity/__tests__/effect.spec.ts

-39
Original file line numberDiff line numberDiff line change
@@ -990,44 +990,5 @@ describe('reactivity/effect', () => {
990990
expect(fnSpy).toHaveBeenCalledTimes(3)
991991
expect(has).toBe(false)
992992
})
993-
994-
test('pause execution of side effect functions', () => {
995-
const obj = reactive({ foo: 1 })
996-
const fnSpy = vi.fn(() => obj.foo)
997-
998-
const runner = effect(fnSpy)
999-
1000-
expect(fnSpy).toHaveBeenCalledTimes(1)
1001-
obj.foo++
1002-
expect(fnSpy).toHaveBeenCalledTimes(2)
1003-
1004-
runner.effect.pause()
1005-
obj.foo++
1006-
expect(fnSpy).toHaveBeenCalledTimes(2)
1007-
1008-
runner.effect.resume()
1009-
1010-
obj.foo++
1011-
expect(fnSpy).toHaveBeenCalledTimes(3)
1012-
})
1013-
1014-
test('immediately execute the calls during the pause when resuming', () => {
1015-
const obj = reactive({ bar: 1 })
1016-
const fnSpy = vi.fn(() => obj.bar)
1017-
1018-
const runner = effect(fnSpy)
1019-
1020-
expect(fnSpy).toHaveBeenCalledTimes(1)
1021-
obj.bar++
1022-
expect(fnSpy).toHaveBeenCalledTimes(2)
1023-
1024-
runner.effect.pause()
1025-
obj.bar++
1026-
expect(fnSpy).toHaveBeenCalledTimes(2)
1027-
1028-
runner.effect.resume(true)
1029-
1030-
expect(fnSpy).toHaveBeenCalledTimes(3)
1031-
})
1032993
})
1033994
})

packages/reactivity/src/effect.ts

-30
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,6 @@ export class ReactiveEffect<T = any> {
6868
* @internal
6969
*/
7070
private deferStop?: boolean
71-
/**
72-
* Whether to pause
73-
* @internal
74-
*/
75-
private isPaused = false
76-
/**
77-
* Indicates whether the run method was called during the pause process
78-
* @internal
79-
*/
80-
private isCalled = false
8171

8272
onStop?: () => void
8373
// dev only
@@ -93,25 +83,7 @@ export class ReactiveEffect<T = any> {
9383
recordEffectScope(this, scope)
9484
}
9585

96-
pause() {
97-
this.isPaused = true
98-
}
99-
100-
resume(runOnResume = false) {
101-
if (this.isPaused) {
102-
this.isPaused = false
103-
if (runOnResume && this.isCalled) {
104-
this.run()
105-
}
106-
this.isCalled = false
107-
}
108-
}
109-
11086
run() {
111-
if (this.isPaused) {
112-
this.isCalled = true
113-
return
114-
}
11587
if (!this.active) {
11688
return this.fn()
11789
}
@@ -154,8 +126,6 @@ export class ReactiveEffect<T = any> {
154126
}
155127

156128
stop() {
157-
// Reset the paused state first when stopping
158-
this.resume()
159129
// stopped while running itself - defer the cleanup
160130
if (activeEffect === this) {
161131
this.deferStop = true

packages/runtime-core/__tests__/components/KeepAlive.spec.ts

+45
Original file line numberDiff line numberDiff line change
@@ -977,4 +977,49 @@ describe('KeepAlive', () => {
977977
expect(mountedB).toHaveBeenCalledTimes(1)
978978
expect(unmountedB).toHaveBeenCalledTimes(0)
979979
})
980+
981+
test('should resume/pause update in activated/deactivated', async () => {
982+
const renderA = vi.fn(() => 'A')
983+
const msg = ref('hello')
984+
const A = {
985+
render: () => h('div', [renderA(), msg.value])
986+
}
987+
const B = {
988+
render: () => 'B'
989+
}
990+
991+
const current = shallowRef(A)
992+
const app = createApp({
993+
setup() {
994+
return () => {
995+
return [h(KeepAlive, h(current.value))]
996+
}
997+
}
998+
})
999+
1000+
app.mount(root)
1001+
1002+
expect(serializeInner(root)).toBe(`<div>Ahello</div>`)
1003+
expect(renderA).toHaveBeenCalledTimes(1)
1004+
msg.value = 'world'
1005+
await nextTick()
1006+
expect(serializeInner(root)).toBe(`<div>Aworld</div>`)
1007+
expect(renderA).toHaveBeenCalledTimes(2)
1008+
1009+
// @ts-expect-error
1010+
current.value = B
1011+
await nextTick()
1012+
expect(serializeInner(root)).toBe(`B`)
1013+
expect(renderA).toHaveBeenCalledTimes(2)
1014+
1015+
msg.value = 'hello world'
1016+
await nextTick()
1017+
expect(serializeInner(root)).toBe(`B`)
1018+
expect(renderA).toHaveBeenCalledTimes(2)
1019+
1020+
current.value = A
1021+
await nextTick()
1022+
expect(serializeInner(root)).toBe(`<div>Ahello world</div>`)
1023+
expect(renderA).toHaveBeenCalledTimes(3)
1024+
})
9801025
})

packages/runtime-core/src/component.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import {
88
EffectScope,
99
markRaw,
1010
track,
11-
TrackOpTypes,
12-
ReactiveEffect
11+
TrackOpTypes
1312
} from '@vue/reactivity'
1413
import {
1514
ComponentPublicInstance,
@@ -79,6 +78,7 @@ import {
7978
} from './compat/compatConfig'
8079
import { SchedulerJob } from './scheduler'
8180
import { LifecycleHooks } from './enums'
81+
import { RenderEffect } from './renderEffect'
8282

8383
export type Data = Record<string, unknown>
8484

@@ -240,7 +240,7 @@ export interface ComponentInternalInstance {
240240
/**
241241
* Render effect instance
242242
*/
243-
effect: ReactiveEffect
243+
effect: RenderEffect
244244
/**
245245
* Bound effect runner to be passed to schedulers
246246
*/
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ReactiveEffect } from '@vue/reactivity'
2+
3+
/**
4+
* Extend `ReactiveEffect` by adding `pause` and `resume` methods for controlling the execution of the `render` function.
5+
*/
6+
export class RenderEffect extends ReactiveEffect {
7+
private _isPaused = false
8+
private _isCalled = false
9+
pause() {
10+
this._isPaused = true
11+
}
12+
resume(runOnce = false) {
13+
if (this._isPaused) {
14+
this._isPaused = false
15+
if (this._isCalled && runOnce) {
16+
super.run()
17+
}
18+
this._isCalled = false
19+
}
20+
}
21+
update() {
22+
if (this._isPaused) {
23+
this._isCalled = true
24+
} else {
25+
return super.run()
26+
}
27+
}
28+
}

packages/runtime-core/src/renderer.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
flushPreFlushCbs,
4646
SchedulerJob
4747
} from './scheduler'
48-
import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity'
48+
import { pauseTracking, resetTracking } from '@vue/reactivity'
4949
import { updateProps } from './componentProps'
5050
import { updateSlots } from './componentSlots'
5151
import { pushWarningContext, popWarningContext, warn } from './warning'
@@ -72,6 +72,7 @@ import { initFeatureFlags } from './featureFlags'
7272
import { isAsyncWrapper } from './apiAsyncComponent'
7373
import { isCompatEnabled } from './compat/compatConfig'
7474
import { DeprecationTypes } from './compat/compatConfig'
75+
import { RenderEffect } from './renderEffect'
7576

7677
export interface Renderer<HostElement = RendererElement> {
7778
render: RootRenderFunction<HostElement>
@@ -1543,14 +1544,14 @@ function baseCreateRenderer(
15431544
}
15441545
}
15451546

1546-
// create reactive effect for rendering
1547-
const effect = (instance.effect = new ReactiveEffect(
1547+
// create render effect for rendering
1548+
const effect = (instance.effect = new RenderEffect(
15481549
componentUpdateFn,
15491550
() => queueJob(update),
15501551
instance.scope // track it in component's effect scope
15511552
))
15521553

1553-
const update: SchedulerJob = (instance.update = () => effect.run())
1554+
const update: SchedulerJob = (instance.update = () => effect.update())
15541555
update.id = instance.uid
15551556
// allowRecurse
15561557
// #1801, #2043 component render effects should allow recursive updates

0 commit comments

Comments
 (0)