Skip to content

Commit

Permalink
refactor(compute): should check deps before compute
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx committed Dec 24, 2024
1 parent 9fcddbc commit 08f4b96
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 25 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ npm add value-enhancer

| import | size(brotli) |
| ----------------------------- | ------------ |
| `*` | 1.81 kB |
| `*` | 1.85 kB |
| `{ readonlyVal, val }` (core) | 1.05 kB |
| `{ from }` | 26 B |
| `{ derive }` | 93 B |
| `{ combine }` | 204 B |
| `{ compute }` | 213 B |
| `{ compute }` | 270 B |
| `{ flattenFrom }` | 227 B |
| `{ flatten }` | 36 B |
| `{ reactiveMap }` | 489 B |
Expand Down
67 changes: 44 additions & 23 deletions src/compute.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ValAgent } from "./agent";
import type { ReadonlyVal, UnwrapVal, ValConfig, ValDisposer } from "./typings";
import { invoke, isVal } from "./utils";
import type { ReadonlyVal, UnwrapVal, ValConfig, ValVersion } from "./typings";
import { INIT_VALUE, isVal, strictEqual } from "./utils";
import { ValImpl } from "./val";

export interface ComputeGet {
Expand Down Expand Up @@ -39,8 +39,8 @@ export const compute = <TValue = any>(
): ReadonlyVal<TValue> => {
let scopeLevel = 0;

let currentDisposers = new Map<ReadonlyVal, ValDisposer>();
let oldDisposers = new Map<ReadonlyVal, ValDisposer>();
let currentDeps = new Map<ReadonlyVal, ValVersion>();
let oldDeps = new Map<ReadonlyVal, ValVersion>();

const get = <T = any>(
val$?: T | ReadonlyVal<T> | { $: ReadonlyVal<T> }
Expand All @@ -52,40 +52,61 @@ export const compute = <TValue = any>(
val$ = (val$ as { $: ReadonlyVal<T> }).$;
}

if (!currentDisposers.has(val$)) {
let disposer = oldDisposers.get(val$);
if (disposer) {
oldDisposers.delete(val$);
} else {
disposer = val$.$valCompute(agent.notify_);
if (currentDeps.has(val$)) {
if (!strictEqual(val$.$version, currentDeps.get(val$))) {
// depends on multiple versions of the same val
// so we set a unique version here
currentDeps.set(val$, INIT_VALUE);
}
currentDisposers.set(val$, disposer);
} else {
if (!oldDeps.delete(val$)) {
val$.$valCompute(agent.notify_);
}
currentDeps.set(val$, val$.$version);
}

return val$.value;
};

let currentValue: TValue;
const agent = new ValAgent(
() => {
if (!scopeLevel++) {
const tmp = currentDisposers;
currentDisposers = oldDisposers;
oldDisposers = tmp;
if (!scopeLevel) {
if (currentDeps.size) {
x: {
for (const [dep, version] of currentDeps) {
if (!strictEqual(dep.$version, version)) {
break x;
}
}
return currentValue;
}
}

const tmp = currentDeps;
currentDeps = oldDeps;
oldDeps = tmp;
scopeLevel++;
}

const value = effect(get);
currentValue = effect(get);

if (!--scopeLevel && oldDisposers.size) {
oldDisposers.forEach(invoke);
oldDisposers.clear();
if (!--scopeLevel && oldDeps.size) {
for (const dep of oldDeps.keys()) {
dep.unsubscribe(agent.notify_);
}
oldDeps.clear();
}

return value;
return currentValue;
},
config,
() => () => {
if (currentDisposers.size) {
currentDisposers.forEach(invoke);
currentDisposers.clear();
if (currentDeps.size) {
for (const dep of currentDeps.keys()) {
dep.unsubscribe(agent.notify_);
}
currentDeps.clear();
}
}
);
Expand Down
32 changes: 32 additions & 0 deletions test/compute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,36 @@ describe("compute", () => {

s$.dispose();
});

it("should check deps before compute", async () => {
const a = val(0);

const spyB = jest.fn();
const b = compute(get => {
spyB();
return get(a) > 10;
});

const spyC = jest.fn();
const c = compute(get => {
spyC();
return get(b) + "";
});

const spySub = jest.fn();
c.reaction(spySub, true);

expect(spyB).toBeCalledTimes(1);
expect(spyC).toBeCalledTimes(1);
expect(spySub).toBeCalledTimes(0);

spyB.mockClear();
spyC.mockClear();

a.set(2);

expect(spyB).toBeCalledTimes(1);
expect(spyC).toBeCalledTimes(0);
expect(spySub).toBeCalledTimes(0);
});
});

0 comments on commit 08f4b96

Please sign in to comment.