Skip to content

Commit 76884c6

Browse files
committed
up
1 parent f505173 commit 76884c6

File tree

3 files changed

+61
-64
lines changed

3 files changed

+61
-64
lines changed

src/common/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export type Computation<T = any> = {
99
state: ComputationState;
1010
sources: Set<Atom | Derived<any, any>>;
1111
isDerived?: boolean;
12-
value: T;
13-
childrenEffect?: Computation[];
12+
value: T; // for effects, this is the cleanup function
13+
childrenEffect?: Computation[]; // only for effects
1414
};
1515

1616
export type customDirectives = Record<

src/runtime/signals.ts

Lines changed: 51 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,28 @@ let Effects: Computation[];
55
let CurrentComputation: Computation;
66

77
export function effect<T>(fn: () => T) {
8-
const unsubscribe = () => {
9-
cleanupComputation(effectComputation);
10-
unsubscribeChildEffect(effectComputation);
11-
};
128
const effectComputation: Computation = {
139
state: ComputationState.STALE,
1410
value: undefined,
1511
compute() {
1612
CurrentComputation = undefined!;
17-
// removing the sources is made by `runComputation`.
18-
unsubscribe();
19-
// reseting the context will be made by `runComputation`.
13+
// `removeSources` is made by `runComputation`.
14+
unsubscribeEffect(effectComputation);
2015
CurrentComputation = effectComputation;
2116
return fn();
2217
},
2318
sources: new Set(),
2419
childrenEffect: [],
2520
};
26-
// Push to the parent effect if any
2721
CurrentComputation?.childrenEffect?.push?.(effectComputation);
28-
runComputation(effectComputation);
22+
updateComputation(effectComputation);
2923

3024
// Remove sources and unsubscribe
3125
return () => {
3226
removeSources(effectComputation);
3327
const currentComputation = CurrentComputation;
3428
CurrentComputation = undefined!;
35-
unsubscribe();
29+
unsubscribeEffect(effectComputation);
3630
CurrentComputation = currentComputation!;
3731
};
3832
}
@@ -51,7 +45,7 @@ export function derived<T>(fn: () => T): () => T {
5145
observers: new Set<Computation>(),
5246
};
5347
onDerived?.(derivedComputation);
54-
runComputation(derivedComputation);
48+
updateComputation(derivedComputation);
5549
return derivedComputation.value;
5650
};
5751
}
@@ -61,9 +55,8 @@ export function onReadAtom(atom: Atom) {
6155
CurrentComputation.sources!.add(atom);
6256
atom.observers.add(CurrentComputation);
6357
}
64-
6558
export function onWriteAtom(atom: Atom) {
66-
runUpdates(() => {
59+
stackEffects(() => {
6760
for (const ctx of atom.observers) {
6861
if (ctx.state === ComputationState.EXECUTED) {
6962
ctx.state = ComputationState.STALE;
@@ -74,14 +67,20 @@ export function onWriteAtom(atom: Atom) {
7467
});
7568
batchProcessEffects();
7669
}
70+
export function makeAtom(): Atom {
71+
const atom: Atom = {
72+
value: undefined,
73+
observers: new Set(),
74+
};
75+
return atom;
76+
}
7777

7878
export function getCurrentComputation() {
7979
return CurrentComputation;
8080
}
8181
export function setComputation(computation: Computation) {
8282
CurrentComputation = computation;
8383
}
84-
8584
export function runWithComputation<T>(computation: Computation, fn: () => T): T {
8685
const currentComputation = CurrentComputation;
8786
CurrentComputation = computation;
@@ -93,45 +92,27 @@ export function runWithComputation<T>(computation: Computation, fn: () => T): T
9392
}
9493
return result;
9594
}
96-
9795
export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): ReturnType<T> {
9896
return runWithComputation(undefined!, fn);
9997
}
10098

101-
export function makeAtom(): Atom {
102-
const atom: Atom = {
103-
value: undefined,
104-
observers: new Set(),
105-
};
106-
return atom;
107-
}
108-
109-
function runComputation(computation: Computation) {
99+
function updateComputation(computation: Computation) {
110100
const state = computation.state;
111101
computation.isDerived && onReadAtom(computation as Derived<any, any>);
112102
if (state === ComputationState.EXECUTED) return;
113103
if (state === ComputationState.PENDING) {
114104
computeSources(computation as Derived<any, any>);
115105
}
116-
const executionContext = CurrentComputation;
117106
// todo: test performance. We might want to avoid removing the atoms to
118107
// directly re-add them at compute. Especially as we are making them stale.
119108
removeSources(computation);
109+
const executionContext = CurrentComputation;
120110
CurrentComputation = computation;
121111
computation.value = computation.compute?.();
122112
computation.state = ComputationState.EXECUTED;
123113
CurrentComputation = executionContext;
124114
}
125115

126-
const batchProcessEffects = batched(processEffects);
127-
function processEffects() {
128-
if (!Effects) return;
129-
for (const computation of Effects) {
130-
runComputation(computation);
131-
}
132-
Effects = undefined!;
133-
}
134-
135116
function removeSources(computation: Computation) {
136117
const sources = computation.sources;
137118
for (const source of sources) {
@@ -148,49 +129,62 @@ function removeSources(computation: Computation) {
148129
sources.clear();
149130
}
150131

132+
function stackEffects(fn: Function) {
133+
if (Effects) return fn();
134+
Effects = [];
135+
try {
136+
return fn();
137+
} finally {
138+
// processEffects();
139+
true;
140+
}
141+
}
142+
const batchProcessEffects = batched(processEffects);
143+
function processEffects() {
144+
if (!Effects) return;
145+
for (const computation of Effects) {
146+
updateComputation(computation);
147+
}
148+
Effects = undefined!;
149+
}
150+
151+
function unsubscribeEffect(effectComputation: Computation) {
152+
cleanupEffect(effectComputation);
153+
unsubscribeChildEffect(effectComputation);
154+
}
151155
/**
152156
* Unsubscribe an execution context and all its children from all atoms
153157
* they are subscribed to.
154158
*
155-
* @param parentExecutionContext the context to unsubscribe
159+
* @param parentEffect the context to unsubscribe
156160
*/
157-
function unsubscribeChildEffect(parentExecutionContext: Computation) {
158-
for (const children of parentExecutionContext.childrenEffect!) {
159-
cleanupComputation(children);
161+
function unsubscribeChildEffect(parentEffect: Computation) {
162+
for (const children of parentEffect.childrenEffect!) {
163+
cleanupEffect(children);
160164
removeSources(children);
161165
// Consider it executed to avoid it's re-execution
162166
children.state = ComputationState.EXECUTED;
163167
unsubscribeChildEffect(children);
164168
}
165-
parentExecutionContext.childrenEffect!.length = 0;
169+
parentEffect.childrenEffect!.length = 0;
166170
}
167-
168-
function cleanupComputation(computation: Computation) {
171+
function cleanupEffect(computation: Computation) {
169172
// the computation.value of an effect is a cleanup function
170-
if (computation.value && typeof computation.value === "function") {
171-
computation.value();
173+
const cleanupFn = computation.value;
174+
if (cleanupFn && typeof cleanupFn === "function") {
175+
cleanupFn();
172176
computation.value = undefined;
173177
}
174178
}
175179

176-
function runUpdates(fn: Function) {
177-
if (Effects) return fn();
178-
Effects = [];
179-
try {
180-
return fn();
181-
} finally {
182-
// processEffects();
183-
true;
184-
}
185-
}
186180
function computeSources(derived: Derived<any, any>) {
187181
for (const source of derived.sources) {
188182
if ("sources" in source) continue;
189-
computeMemo(source as Derived<any, any>);
183+
computeDerived(source as Derived<any, any>);
190184
}
191185
}
192186

193-
function computeMemo(derived: Derived<any, any>) {
187+
function computeDerived(derived: Derived<any, any>) {
194188
if (derived.state === ComputationState.EXECUTED) {
195189
onReadAtom(derived);
196190
return derived.value;
@@ -212,10 +206,10 @@ function markDownstream<A, B>(derived: Derived<A, B>) {
212206
}
213207

214208
// For tests
215-
// todo: find a better way to test
209+
216210
let onDerived: (derived: Derived<any, any>) => void;
217211

218-
export function setSginalHooks(hooks: { onDerived: (derived: Derived<any, any>) => void }) {
212+
export function setSignalHooks(hooks: { onDerived: (derived: Derived<any, any>) => void }) {
219213
if (hooks.onDerived) onDerived = hooks.onDerived;
220214
}
221215
export function resetSignalHooks() {

tests/derived.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { reactive, effect } from "../src";
22
import { Derived } from "../src/common/types";
3-
import { derived, resetSignalHooks, setSginalHooks } from "../src/runtime/signals";
3+
import { derived, resetSignalHooks, setSignalHooks } from "../src/runtime/signals";
44
// import * as signals from "../src/runtime/signals";
55
import { expectSpy, nextMicroTick } from "./helpers";
66

@@ -170,13 +170,16 @@ describe("derived", () => {
170170
});
171171
});
172172
describe("unsubscription", () => {
173-
let memos: Derived<any, any>[] = [];
174-
beforeEach(() => {
175-
setSginalHooks({ onDerived: (m: Derived<any, any>) => memos.push(m) });
173+
const memos: Derived<any, any>[] = [];
174+
beforeAll(() => {
175+
setSignalHooks({ onDerived: (m: Derived<any, any>) => memos.push(m) });
176176
});
177-
afterEach(() => {
177+
afterAll(() => {
178178
resetSignalHooks();
179179
});
180+
afterEach(() => {
181+
memos.length = 0;
182+
});
180183

181184
test("derived shoud unsubscribes from dependencies when effect is unsubscribed", async () => {
182185
const state = reactive({ a: 1, b: 2 });

0 commit comments

Comments
 (0)