diff --git a/.changeset/tasty-years-hammer.md b/.changeset/tasty-years-hammer.md new file mode 100644 index 0000000000..42b559ac9f --- /dev/null +++ b/.changeset/tasty-years-hammer.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add getOrInsert/getOrInsertComputed to SvelteMap diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index 48d06a05a7..935db8fcb1 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -153,6 +153,28 @@ export class SvelteMap extends Map { return super.get(key); } + /** + * @param {K} key + * @param {V} value + * */ + getOrInsert(key, value) { + if (!super.has(key)) { + this.set(key, value); + } + return /** @type {V} */ (this.get(key)); + } + + /** + * @param {K} key + * @param {(key: K) => V} callbackFn + */ + getOrInsertComputed(key, callbackFn) { + if (!super.has(key)) { + this.set(key, callbackFn(key)); + } + return /** @type {V} */ (this.get(key)); + } + /** * @param {K} key * @param {V} value diff --git a/packages/svelte/src/reactivity/map.test.ts b/packages/svelte/src/reactivity/map.test.ts index 8bb6f72f7b..f5cbb21923 100644 --- a/packages/svelte/src/reactivity/map.test.ts +++ b/packages/svelte/src/reactivity/map.test.ts @@ -100,6 +100,85 @@ test('map.get(...)', () => { cleanup(); }); +test('map.getOrInsert(...)', () => { + const map = new SvelteMap([ + [2, 2], + [3, 3] + ]); + + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push('get 1', map.getOrInsert(1, 1)); + }); + + render_effect(() => { + log.push('get 2', map.getOrInsert(2, 2)); + }); + + render_effect(() => { + log.push('get 3', map.getOrInsert(3, 4)); + }); + }); + + flushSync(() => { + map.delete(2); + }); + + flushSync(() => { + map.set(2, 2); + }); + + assert.deepEqual(log, ['get 1', 1, 'get 2', 2, 'get 3', 3, 'get 2', 2]); + + cleanup(); +}); + +test('map.getOrInsertComputed(...)', () => { + const map = new SvelteMap([ + [2, 2], + [3, 3] + ]); + + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push( + 'get 1', + map.getOrInsertComputed(1, (k) => k) + ); + }); + + render_effect(() => { + log.push( + 'get 2', + map.getOrInsertComputed(2, (k) => k) + ); + }); + + render_effect(() => { + log.push( + 'get 3', + map.getOrInsertComputed(3, () => 4) + ); + }); + }); + + flushSync(() => { + map.delete(2); + }); + + flushSync(() => { + map.set(2, 2); + }); + + assert.deepEqual(log, ['get 1', 1, 'get 2', 2, 'get 3', 3, 'get 2', 2]); + + cleanup(); +}); + test('map.has(...)', () => { const map = new SvelteMap([ [1, 1], diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index dcc6dc5914..9e69796691 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2466,6 +2466,10 @@ declare module 'svelte/reactivity' { constructor(value?: Iterable | null | undefined); + getOrInsert(key: K, value: V): V; + + getOrInsertComputed(key: K, callbackFn: (key: K) => V): V; + set(key: K, value: V): this; #private; }