diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index d3f0c1eaf..f244ea02a 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -24,20 +24,15 @@ export class ReactiveMap extends Map { #keyTriggers = new TriggerCache(); #valueTriggers = new TriggerCache(); - constructor(initial?: Iterable | null) { + constructor(entries?: readonly (readonly [K, V])[] | null) { super(); - if (initial) for (const v of initial) super.set(v[0], v[1]); + if (entries) for (const entry of entries) super.set(...entry); } - // reads - has(key: K): boolean { - this.#keyTriggers.track(key); - return super.has(key); - } - get(key: K): V | undefined { - this.#valueTriggers.track(key); - return super.get(key); + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); } + get size(): number { this.#keyTriggers.track($KEYS); return super.size; @@ -48,73 +43,90 @@ export class ReactiveMap extends Map { this.#keyTriggers.track(key); yield key; } + this.#keyTriggers.track($KEYS); } + *values(): IterableIterator { - for (const [key, v] of super.entries()) { + for (const [key, value] of super.entries()) { this.#valueTriggers.track(key); - yield v; + yield value; } + this.#keyTriggers.track($KEYS); } + *entries(): IterableIterator<[K, V]> { for (const entry of super.entries()) { + this.#keyTriggers.track(entry[0]); this.#valueTriggers.track(entry[0]); yield entry; } + + this.#keyTriggers.track($KEYS); + } + + forEach(fn: (value: V, key: K, map: Map) => void): void { this.#keyTriggers.track($KEYS); + + for (const [key, value] of super.entries()) { + this.#keyTriggers.track(key); + this.#valueTriggers.track(key); + fn(value, key, this); + } + } + + has(key: K): boolean { + this.#keyTriggers.track(key); + return super.has(key); + } + + get(key: K): V | undefined { + this.#valueTriggers.track(key); + return super.get(key); } - // writes set(key: K, value: V): this { + const hasKey = super.has(key); + const currentValue = super.get(key); + const result = super.set(key, value); + batch(() => { - if (super.has(key)) { - if (super.get(key)! === value) return; - } else { + if (!hasKey) { this.#keyTriggers.dirty(key); this.#keyTriggers.dirty($KEYS); } - this.#valueTriggers.dirty(key); - super.set(key, value); + + if (value !== currentValue) this.#valueTriggers.dirty(key); }); - return this; + + return result; } + delete(key: K): boolean { - const r = super.delete(key); - if (r) { + const currentValue = super.get(key); + const result = super.delete(key); + + if (result) { batch(() => { this.#keyTriggers.dirty(key); this.#keyTriggers.dirty($KEYS); - this.#valueTriggers.dirty(key); + if (currentValue !== undefined) this.#valueTriggers.dirty(key); }); } - return r; + + return result; } + clear(): void { if (super.size) { + super.clear(); batch(() => { - for (const v of super.keys()) { - this.#keyTriggers.dirty(v); - this.#valueTriggers.dirty(v); - } - super.clear(); - this.#keyTriggers.dirty($KEYS); + this.#keyTriggers.dirtyAll(); + this.#valueTriggers.dirtyAll(); }); } } - - // callback - forEach(callbackfn: (value: V, key: K, map: this) => void) { - this.#keyTriggers.track($KEYS); - for (const [key, v] of super.entries()) { - this.#valueTriggers.track(key); - callbackfn(v, key, this); - } - } - - [Symbol.iterator](): IterableIterator<[K, V]> { - return this.entries(); - } } /** @@ -137,38 +149,46 @@ export class ReactiveWeakMap extends WeakMap { #keyTriggers = new TriggerCache(WeakMap); #valueTriggers = new TriggerCache(WeakMap); - constructor(initial?: Iterable | null) { + constructor(entries?: readonly [K, V][] | null) { super(); - if (initial) for (const v of initial) super.set(v[0], v[1]); + if (entries) for (const entry of entries) super.set(...entry); } has(key: K): boolean { this.#keyTriggers.track(key); return super.has(key); } + get(key: K): V | undefined { this.#valueTriggers.track(key); return super.get(key); } + set(key: K, value: V): this { + const hasKey = super.has(key); + const currentValue = super.get(key); + const result = super.set(key, value); + batch(() => { - if (super.has(key)) { - if (super.get(key)! === value) return; - } else this.#keyTriggers.dirty(key); - this.#valueTriggers.dirty(key); - super.set(key, value); + if (!hasKey) this.#keyTriggers.dirty(key); + if (value !== currentValue) this.#valueTriggers.dirty(key); }); - return this; + + return result; } + delete(key: K): boolean { - const r = super.delete(key); - if (r) { + const currentValue = super.get(key); + const result = super.delete(key); + + if (result) { batch(() => { this.#keyTriggers.dirty(key); - this.#valueTriggers.dirty(key); + if (currentValue !== undefined) this.#valueTriggers.dirty(key); }); } - return r; + + return result; } } @@ -176,14 +196,16 @@ export class ReactiveWeakMap extends WeakMap { export type SignalMap = Accessor<[K, V][]> & ReactiveMap; /** @deprecated */ -export function createMap(initial?: [K, V][]): SignalMap { - const map = new ReactiveMap(initial); +export function createMap(entries?: readonly (readonly [K, V])[] | null): SignalMap { + const map = new ReactiveMap(entries); return new Proxy(() => [...map], { get: (_, b) => map[b as keyof typeof map], }) as SignalMap; } /** @deprecated */ -export function createWeakMap(initial?: [K, V][]): ReactiveWeakMap { - return new ReactiveWeakMap(initial); +export function createWeakMap( + entries?: readonly [K, V][] | null, +): ReactiveWeakMap { + return new ReactiveWeakMap(entries); } diff --git a/packages/set/src/index.ts b/packages/set/src/index.ts index e22b8ce79..4dbe1d71a 100644 --- a/packages/set/src/index.ts +++ b/packages/set/src/index.ts @@ -18,74 +18,84 @@ const $KEYS = Symbol("track-keys"); export class ReactiveSet extends Set { #triggers = new TriggerCache(); - constructor(values?: Iterable | null) { + constructor(values?: readonly T[] | null) { super(); - if (values) for (const v of values) super.add(v); + if (values) for (const value of values) super.add(value); + } + + [Symbol.iterator](): IterableIterator { + return this.values(); } - // reads get size(): number { this.#triggers.track($KEYS); return super.size; } - has(v: T): boolean { - this.#triggers.track(v); - return super.has(v); + + keys(): IterableIterator { + return this.values(); } - *keys(): IterableIterator { - for (const key of super.keys()) { - this.#triggers.track(key); - yield key; + *values(): IterableIterator { + for (const value of super.values()) { + this.#triggers.track(value); + yield value; } this.#triggers.track($KEYS); } - values(): IterableIterator { - return this.keys(); - } + *entries(): IterableIterator<[T, T]> { - for (const key of super.keys()) { - this.#triggers.track(key); - yield [key, key]; + for (const entry of super.entries()) { + this.#triggers.track(entry[0]); + yield entry; } this.#triggers.track($KEYS); } - [Symbol.iterator](): IterableIterator { - return this.values(); - } - forEach(callbackfn: (value: T, value2: T, set: this) => void) { + forEach(fn: (value1: T, value2: T, set: Set) => void): void { this.#triggers.track($KEYS); - super.forEach(callbackfn as any); + + for (const value of super.values()) { + this.#triggers.track(value); + fn(value, value, this); + } } - // writes - add(v: T): this { - if (!super.has(v)) { - super.add(v); + has(value: T): boolean { + this.#triggers.track(value); + return super.has(value); + } + + add(value: T): this { + if (!super.has(value)) { + super.add(value); batch(() => { - this.#triggers.dirty(v); + this.#triggers.dirty(value); this.#triggers.dirty($KEYS); }); } + return this; } - delete(v: T): boolean { - const r = super.delete(v); - if (r) { + + delete(value: T): boolean { + const result = super.delete(value); + + if (result) { batch(() => { - this.#triggers.dirty(v); + this.#triggers.dirty(value); this.#triggers.dirty($KEYS); }); } - return r; + + return result; } + clear(): void { if (super.size) { + super.clear(); batch(() => { - for (const v of super.keys()) this.#triggers.dirty(v); - super.clear(); - this.#triggers.dirty($KEYS); + this.#triggers.dirtyAll(); }); } } @@ -104,26 +114,30 @@ export class ReactiveSet extends Set { export class ReactiveWeakSet extends WeakSet { #triggers = new TriggerCache(WeakMap); - constructor(values?: Iterable | null) { + constructor(values?: readonly T[] | null) { super(); - if (values) for (const v of values) super.add(v); + if (values) for (const value of values) super.add(value); } - has(v: T): boolean { - this.#triggers.track(v); - return super.has(v); + has(value: T): boolean { + this.#triggers.track(value); + return super.has(value); } - add(v: T): this { - if (!super.has(v)) { - super.add(v); - this.#triggers.dirty(v); + + add(value: T): this { + if (!super.has(value)) { + super.add(value); + this.#triggers.dirty(value); } return this; } - delete(v: T): boolean { - const deleted = super.delete(v); - deleted && this.#triggers.dirty(v); - return deleted; + + delete(value: T): boolean { + const result = super.delete(value); + + result && this.#triggers.dirty(value); + + return result; } } @@ -131,14 +145,16 @@ export class ReactiveWeakSet extends WeakSet { export type SignalSet = Accessor & ReactiveSet; /** @deprecated */ -export function createSet(initial?: T[]): SignalSet { - const set = new ReactiveSet(initial); +export function createSet(values?: readonly T[] | null): SignalSet { + const set = new ReactiveSet(values); return new Proxy(() => [...set], { get: (_, b) => set[b as keyof ReactiveSet], }) as SignalSet; } /** @deprecated */ -export function createWeakSet(initial?: T[]): ReactiveWeakSet { - return new ReactiveWeakSet(initial); +export function createWeakSet( + values?: readonly T[] | null, +): ReactiveWeakSet { + return new ReactiveWeakSet(values); } diff --git a/packages/trigger/src/index.ts b/packages/trigger/src/index.ts index e91011632..440b727d9 100644 --- a/packages/trigger/src/index.ts +++ b/packages/trigger/src/index.ts @@ -50,6 +50,11 @@ export class TriggerCache { this.#map.get(key)?.$$(); } + dirtyAll() { + if (isServer) return; + for (const trigger of this.#map.values()) trigger.$$(); + } + track(key: T) { if (!getListener()) return; let trigger = this.#map.get(key); @@ -93,7 +98,7 @@ export class TriggerCache { */ export function createTriggerCache( mapConstructor: WeakMapConstructor | MapConstructor = Map, -): [track: (key: T) => void, dirty: (key: T) => void] { +): [track: (key: T) => void, dirty: (key: T) => void, dirtyAll: () => void] { const map = new TriggerCache(mapConstructor); - return [map.track.bind(map), map.dirty.bind(map)]; + return [map.track.bind(map), map.dirty.bind(map), map.dirtyAll.bind(map)]; } diff --git a/packages/trigger/test/index.test.ts b/packages/trigger/test/index.test.ts index 854c776bd..2e5b54645 100644 --- a/packages/trigger/test/index.test.ts +++ b/packages/trigger/test/index.test.ts @@ -22,6 +22,21 @@ describe("createTriggerCache", () => { dispose(); })); + test("dirtyAll", () => + createRoot(dispose => { + const [track, , dirtyAll] = createTriggerCache(Map); + let runs = -1; + createComputed(() => { + track(1); + runs++; + }); + expect(runs).toBe(0); + dirtyAll(); + expect(runs).toBe(1); + + dispose(); + })); + test("weak trigger cache", () => createRoot(dispose => { const [track, dirty] = createTriggerCache();