Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reactivity): add pause/resume methods to ReactiveEffect #9651

Merged
merged 46 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5a279be
feat: add `pause/resume` function to `ReactiveEffect`
Alfred-Skyblue Sep 13, 2023
9bda252
chore: rollback
Alfred-Skyblue Sep 20, 2023
c2b7333
feat: extend `ReactiveEffect` using derived class `RenderEffect`
Alfred-Skyblue Sep 20, 2023
df77be4
Merge branch 'main' into feat-effect
Alfred-Skyblue Sep 25, 2023
a5446a3
Merge branch 'main' into feat-effect
Alfred-Skyblue Oct 17, 2023
540e408
Merge branch 'main' into feat-effect
Alfred-Skyblue Oct 19, 2023
a4e43a8
Merge branch 'main' into feat-effect
Alfred-Skyblue Oct 23, 2023
ac2dcd6
Merge branch 'main' into feat-effect
Alfred-Skyblue Oct 23, 2023
b91a0a1
Merge branch 'main' into feat-effect
Alfred-Skyblue Nov 2, 2023
92419a2
Merge branch 'main' into feat-effect
Alfred-Skyblue Nov 7, 2023
34e569c
feat(KeepAlive.ts): use the `lazy` prop to control updates
Alfred-Skyblue Nov 13, 2023
88a6ac6
test: update unit test
Alfred-Skyblue Nov 13, 2023
1c5b52d
Merge branch 'main' into feat-effect
Alfred-Skyblue Nov 14, 2023
55b2ca5
feat(reactivity): EffectScope adds pause and resume function
Alfred-Skyblue Nov 16, 2023
7ed2b07
feat(runtime-core): mark effectScope in component as not detached
Alfred-Skyblue Nov 16, 2023
cb4faf8
types(effectScope): add constructor overload
Alfred-Skyblue Nov 16, 2023
6ffcc0b
docs: update comments
Alfred-Skyblue Nov 21, 2023
930fa3e
test: add unit test
Alfred-Skyblue Nov 22, 2023
0a8f82e
Merge branch 'main' into feat/effect/pause-resume
Alfred-Skyblue Nov 28, 2023
d8754f3
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Nov 30, 2023
b0e64fe
chore: rollback component EffectScope
Alfred-Skyblue Dec 1, 2023
791c904
chore: rollback keepAlive
Alfred-Skyblue Dec 1, 2023
f2de749
feat: Intercept `scheduler` execution
Alfred-Skyblue Dec 1, 2023
7a4c366
feat(apiWatch): add pause and resume methods to WatchHandle
Alfred-Skyblue Dec 1, 2023
22cfc8e
test: update unit test
Alfred-Skyblue Dec 2, 2023
381e5f2
feat(apiWatch): add the `immediate` parameter for the `watch` functio…
Alfred-Skyblue Dec 2, 2023
baa4d4d
chore: rename attribute name
Alfred-Skyblue Dec 2, 2023
214e77f
chore: simplify effect.ts
Alfred-Skyblue Dec 4, 2023
14f0919
feat: do not allow calling the resume method after stopping
Alfred-Skyblue Dec 4, 2023
1c3facf
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Dec 5, 2023
401418c
chore: rollback renderer.ts
Alfred-Skyblue Dec 5, 2023
f782c6b
chore: rollback KeepAlive.ts
Alfred-Skyblue Dec 5, 2023
9e7c1e5
chore(effectScope): remove the `parent` parameter from EffectScope
Alfred-Skyblue Dec 5, 2023
287c182
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Dec 5, 2023
406268d
feat: `effect` is always fired once on recovery
Alfred-Skyblue Dec 6, 2023
98cfd71
chore: in SSR, when not in sync mode, directly return an NOOP
Alfred-Skyblue Dec 6, 2023
4872137
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Dec 12, 2023
4b9026a
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Dec 21, 2023
fde852b
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Dec 28, 2023
bbfeb9d
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Jan 3, 2024
ef2ad1b
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Jan 11, 2024
a1426d5
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Feb 18, 2024
108eb94
Merge branch 'minor' into feat/effect/pause-resume
Alfred-Skyblue Mar 6, 2024
272fb51
chore: Merge branch 'minor' into feat/effect/pause-resume
yyx990803 Aug 2, 2024
e2e6eae
refactor: simplify some code
yyx990803 Aug 2, 2024
d836595
chore: more simplifications
yyx990803 Aug 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/reactivity/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class ReactiveEffect<T = any> {
*/
private deferStop?: boolean

private _isPaused = false

private _isCalled = false

onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
Expand All @@ -83,7 +87,24 @@ export class ReactiveEffect<T = any> {
recordEffectScope(this, scope)
}

pause() {
this._isPaused = true
}

resume(runOnce = false) {
Alfred-Skyblue marked this conversation as resolved.
Show resolved Hide resolved
if (this._isPaused) {
this._isPaused = false
if (this._isCalled && runOnce) {
this.run()
}
this._isCalled = false
}
}
run() {
if (this._isPaused) {
this._isCalled = true
return
}
if (!this.active) {
return this.fn()
}
Expand Down
55 changes: 46 additions & 9 deletions packages/reactivity/src/effectScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export class EffectScope {
*/
cleanups: (() => void)[] = []

private _isPaused = false

/**
* only assigned by undetached scope
* @internal
Expand All @@ -34,20 +36,52 @@ export class EffectScope {
*/
private index: number | undefined

constructor(public detached = false) {
this.parent = activeEffectScope
if (!detached && activeEffectScope) {
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1
constructor(detached: false, parent?: EffectScope | undefined)
constructor(detached?: boolean)
constructor(
public detached = false,
parent = activeEffectScope
) {
this.parent = parent
if (!detached && parent) {
this.index = (parent.scopes || (parent.scopes = [])).push(this) - 1
}
}

get active() {
return this._active
}

pause() {
if (this._active) {
this._isPaused = true
if (this.scopes) {
for (let i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].pause()
}
}
for (let i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].pause()
}
Alfred-Skyblue marked this conversation as resolved.
Show resolved Hide resolved
}
}

resume(runOnce = false) {
if (this._active) {
if (this._isPaused) {
this._isPaused = false
if (this.scopes) {
for (let i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].resume(runOnce)
}
}
for (let i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].resume(runOnce)
}
}
}
}

run<T>(fn: () => T): T | undefined {
if (this._active) {
const currentEffectScope = activeEffectScope
Expand Down Expand Up @@ -114,10 +148,13 @@ export class EffectScope {
* corresponding {@link https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md | RFC}.
*
* @param detached - Can be used to create a "detached" effect scope.
* @param parent - Can be passed to explicitly set the parent scope.
* @see {@link https://vuejs.org/api/reactivity-advanced.html#effectscope}
*/
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
export function effectScope(detached: false, parent?: EffectScope): EffectScope
export function effectScope(detached?: boolean): EffectScope
export function effectScope(detached?: boolean, parent?: EffectScope) {
return new EffectScope(detached as any, parent)
}

export function recordEffectScope(
Expand Down
45 changes: 45 additions & 0 deletions packages/runtime-core/__tests__/components/KeepAlive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,4 +977,49 @@ describe('KeepAlive', () => {
expect(mountedB).toHaveBeenCalledTimes(1)
expect(unmountedB).toHaveBeenCalledTimes(0)
})

test('should resume/pause update in activated/deactivated', async () => {
const renderA = vi.fn(() => 'A')
const msg = ref('hello')
const A = {
render: () => h('div', [renderA(), msg.value])
}
const B = {
render: () => 'B'
}

const current = shallowRef(A)
const app = createApp({
setup() {
return () => {
return [h(KeepAlive, { lazy: true }, h(current.value))]
}
}
})

app.mount(root)

expect(serializeInner(root)).toBe(`<div>Ahello</div>`)
expect(renderA).toHaveBeenCalledTimes(1)
msg.value = 'world'
await nextTick()
expect(serializeInner(root)).toBe(`<div>Aworld</div>`)
expect(renderA).toHaveBeenCalledTimes(2)

// @ts-expect-error
current.value = B
await nextTick()
expect(serializeInner(root)).toBe(`B`)
expect(renderA).toHaveBeenCalledTimes(2)

msg.value = 'hello world'
await nextTick()
expect(serializeInner(root)).toBe(`B`)
expect(renderA).toHaveBeenCalledTimes(2)

current.value = A
await nextTick()
expect(serializeInner(root)).toBe(`<div>Ahello world</div>`)
expect(renderA).toHaveBeenCalledTimes(3)
})
})
2 changes: 1 addition & 1 deletion packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ export function createComponentInstance(
subTree: null!, // will be set synchronously right after creation
effect: null!,
update: null!, // will be set synchronously right after creation
scope: new EffectScope(true /* detached */),
scope: new EffectScope(false, parent?.scope),
Alfred-Skyblue marked this conversation as resolved.
Show resolved Hide resolved
render: null,
proxy: null,
exposed: null,
Expand Down
16 changes: 15 additions & 1 deletion packages/runtime-core/src/components/KeepAlive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface KeepAliveProps {
include?: MatchPattern
exclude?: MatchPattern
max?: number | string
lazy?: boolean
}

type CacheKey = string | number | symbol | ConcreteComponent
Expand Down Expand Up @@ -84,7 +85,8 @@ const KeepAliveImpl: ComponentOptions = {
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
max: [String, Number],
lazy: Boolean
},

setup(props: KeepAliveProps, { slots }: SetupContext) {
Expand Down Expand Up @@ -127,6 +129,12 @@ const KeepAliveImpl: ComponentOptions = {

sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
const instance = vnode.component!

if (props.lazy) {
// on activation, resume the effect of the component instance and immediately execute the call during the pause process
instance.scope.resume(true)
}

move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
// in case props have changed
patch(
Expand Down Expand Up @@ -159,6 +167,12 @@ const KeepAliveImpl: ComponentOptions = {

sharedContext.deactivate = (vnode: VNode) => {
const instance = vnode.component!

if (props.lazy) {
// on deactivation, pause the effect of the component instance
instance.scope.pause()
}

move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
queuePostRenderEffect(() => {
if (instance.da) {
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
flushPreFlushCbs,
SchedulerJob
} from './scheduler'
import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity'
import { pauseTracking, ReactiveEffect, resetTracking } from '@vue/reactivity'
import { updateProps } from './componentProps'
import { updateSlots } from './componentSlots'
import { pushWarningContext, popWarningContext, warn } from './warning'
Expand Down Expand Up @@ -1541,7 +1541,7 @@ function baseCreateRenderer(
}
}

// create reactive effect for rendering
// create render effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
Expand Down