Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 14 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export function setActiveSub(sub?: ReactiveNode) {
return prevSub;
}

function shouldTrack(sub: ReactiveNode): boolean {
return !!(sub.flags & (ReactiveFlags.Mutable | ReactiveFlags.Watching));
}

export function getBatchDepth(): number {
return batchDepth;
}
Expand Down Expand Up @@ -173,13 +177,16 @@ export function effect(fn: () => void | (() => void)): () => void {
flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck,
};
const prevSub = setActiveSub(e);
if (prevSub !== undefined) {
if (prevSub !== undefined && shouldTrack(prevSub)) {
link(e, prevSub, 0);
prevSub.flags |= HasChildEffect;
}
try {
++runDepth;
e.cleanup = e.fn();
} catch (error) {
effectOper.call(e);
throw error;
} finally {
--runDepth;
activeSub = prevSub;
Expand All @@ -197,12 +204,15 @@ export function effectScope(fn: () => void): () => void {
flags: ReactiveFlags.Mutable,
};
const prevSub = setActiveSub(e);
if (prevSub !== undefined) {
if (prevSub !== undefined && shouldTrack(prevSub)) {
link(e, prevSub, 0);
prevSub.flags |= HasChildEffect;
}
try {
fn();
} catch (error) {
effectScopeOper.call(e);
throw error;
} finally {
activeSub = prevSub;
}
Expand Down Expand Up @@ -359,7 +369,7 @@ function computedOper<T>(this: ComputedNode<T>): T {
}
}
const sub = activeSub;
if (sub !== undefined) {
if (sub !== undefined && shouldTrack(sub)) {
link(this, sub, cycle);
}
return this.value!;
Expand Down Expand Up @@ -387,7 +397,7 @@ function signalOper<T>(this: SignalNode<T>, ...value: [T]): T | void {
}
}
const sub = activeSub;
if (sub !== undefined) {
if (sub !== undefined && shouldTrack(sub)) {
link(this, sub, cycle);
}
return this.currentValue;
Expand Down
67 changes: 67 additions & 0 deletions tests/effect-teardown.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect, test } from 'vitest';
import { effect, effectScope, getActiveSub, signal } from '../src';
import { ReactiveFlags, type ReactiveNode } from '../src/system';

test('stopped effect does not subscribe to signals read later in the same run', () => {
const rerun = signal(0);
const readAfterStop = signal(0);
let stop!: () => void;
let node: ReactiveNode | undefined;
let stopDuringRun = false;
let runs = 0;

stop = effect(() => {
node ??= getActiveSub();
runs++;
rerun();
if (stopDuringRun) {
stop();
readAfterStop();
}
});

expect(runs).toBe(1);

stopDuringRun = true;
rerun(1);

expect(runs).toBe(2);
expect(node!.flags).toBe(ReactiveFlags.None);
expect(node!.deps).toBeUndefined();
});

test('failed effect setup does not leave a live subscription behind', () => {
const source = signal(0);
let runs = 0;

expect(() =>
effect(() => {
runs++;
source();
throw new Error('setup failed');
})
).toThrow('setup failed');

expect(runs).toBe(1);
expect(() => source(1)).not.toThrow();
expect(runs).toBe(1);
});

test('failed effect scope setup disposes child effects created before throw', () => {
const source = signal(0);
let childRuns = 0;

expect(() =>
effectScope(() => {
effect(() => {
childRuns++;
source();
});
throw new Error('scope setup failed');
})
).toThrow('scope setup failed');

expect(childRuns).toBe(1);
source(1);
expect(childRuns).toBe(1);
});
Loading