Skip to content

Commit 149eefc

Browse files
authored
Perf: Defer per-row reactive allocations [perf-eval] (#218)
1 parent a221320 commit 149eefc

2 files changed

Lines changed: 12 additions & 9 deletions

File tree

packages/reactivity/src/reaction.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,33 @@ export class Reaction {
77
static create(callback, options = {}) {
88
const reaction = new Reaction(callback, options);
99
if (options.firstRun !== false) {
10-
reaction.boundRun();
10+
reaction.run();
1111
}
1212
return reaction;
1313
}
1414

1515
constructor(callback, { context } = {}) {
1616
this.callback = callback;
1717
this.dependencies = new Set();
18-
this.cleanups = [];
18+
this.cleanups = null; // lazy — most reactions register none
1919
this.firstRun = true;
2020
this.active = true;
2121
if (context && isTracing()) {
2222
this.setContext(context);
2323
}
24-
this.boundRun = this.run.bind(this);
2524
}
2625

2726
// callbacks fire before next run() and on stop. use to scope inner reactions to parent
2827
onCleanup(callback) {
29-
this.cleanups.push(callback);
28+
(this.cleanups ??= []).push(callback);
3029
}
3130

3231
fireCleanups() {
33-
if (this.cleanups.length === 0) {
32+
if (this.cleanups === null) {
3433
return;
3534
}
3635
const callbacks = this.cleanups;
37-
this.cleanups = [];
36+
this.cleanups = null;
3837
for (let i = 0; i < callbacks.length; i++) {
3938
callbacks[i]();
4039
}

packages/renderer/src/engines/native/reactive-context.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ function trapGet(target, prop) {
145145
}
146146
return values[prop];
147147
}
148-
if (!target.keysSealed) { target.keySetVersion.depend(); }
148+
if (!target.keysSealed) { (target.keySetVersion ??= new Dependency()).depend(); }
149149
return target.parent[prop];
150150
}
151151

@@ -269,7 +269,11 @@ export class ReactiveDataContext {
269269
// userland breaks Signal.equalityFunction after this RDC is live,
270270
// both Signal and RDC fail the same way; no divergence.
271271
this.equalityFunction = Signal.equalityFunction;
272-
this.keySetVersion = new Dependency();
272+
// Lazy: only a reader falling through on the unsealed path allocates it
273+
// (spread-mode late keys). as-mode reads run post-seal, so it stays null —
274+
// the per-row saving. A writer fires it only if a reader already created it
275+
// (a changed() on a never-subscribed dep was always a no-op).
276+
this.keySetVersion = null;
273277
this.proxy = new Proxy(this, HANDLER);
274278

275279
if (registerItemContext) {
@@ -288,7 +292,7 @@ export class ReactiveDataContext {
288292
if (key !== this.asKey || value === null || typeof value !== 'object') {
289293
this.deps[key] = new Dependency();
290294
}
291-
if (!this.keysSealed) { this.keySetVersion.changed(); }
295+
if (!this.keysSealed && this.keySetVersion !== null) { this.keySetVersion.changed(); }
292296
return;
293297
}
294298
const old = this.values[key];

0 commit comments

Comments
 (0)