From d3f04043263dea5f3846fa9def3bec9c3de8ae2e Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 29 May 2023 14:10:07 +0700 Subject: [PATCH 1/2] Pass changed boolean array to derived callbacks This allows the callbacks to easily run complex update logic based on which store changed most recently. --- packages/svelte/src/runtime/store/index.js | 7 ++-- packages/svelte/test/store/store.test.js | 39 ++++++++++++++++++++++ site/content/docs/04-run-time.md | 17 ++++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/runtime/store/index.js b/packages/svelte/src/runtime/store/index.js index 1c35cca40a0b..55cdf6126530 100644 --- a/packages/svelte/src/runtime/store/index.js +++ b/packages/svelte/src/runtime/store/index.js @@ -95,7 +95,7 @@ export function writable(value, start = noop) { * @template T * @overload * @param {S} stores - input stores - * @param {(values: import('./public.js').StoresValues, set: import('./public.js').Subscriber, update: (fn: import('./public.js').Updater) => void) => import('./public.js').Unsubscriber | void} fn - function callback that aggregates the values + * @param {(values: import('./public.js').StoresValues, set: import('./public.js').Subscriber, update: (fn: import('./public.js').Updater) => void, changed: boolean[]) => import('./public.js').Unsubscriber | void} fn - function callback that aggregates the values * @param {T} [initial_value] - initial value * @returns {import('./public.js').Readable} */ @@ -133,13 +133,15 @@ export function derived(stores, fn, initial_value) { let started = false; const values = []; let pending = 0; + const changed = []; let cleanup = noop; const sync = () => { if (pending) { return; } cleanup(); - const result = fn(single ? values[0] : values, set, update); + const result = fn(single ? values[0] : values, set, update, changed); + changed.fill(false); if (auto) { set(result); } else { @@ -152,6 +154,7 @@ export function derived(stores, fn, initial_value) { (value) => { values[i] = value; pending &= ~(1 << i); + changed[i] = true; if (started) { sync(); } diff --git a/packages/svelte/test/store/store.test.js b/packages/svelte/test/store/store.test.js index 1f7d90e50c1a..7d5f18692224 100644 --- a/packages/svelte/test/store/store.test.js +++ b/packages/svelte/test/store/store.test.js @@ -375,6 +375,45 @@ describe('store', () => { unsubscribe(); }); + it('provides a boolean array to easily tell what changed', () => { + const count = writable(0); + const values = []; + + const a = derived(count, $count => { + return 'a' + $count; + }); + + const b = derived(count, $count => { + return 'b' + $count; + }); + + const c = writable(0); + + const combined = derived([a, b, c], ([a, b, c], set, _u, changes) => { + const [aChanged, bChanged, cChanged] = changes; + if (aChanged && bChanged) { + set(a + b); + } else if (cChanged) { + set('c' + c); + } else { + set('a or b changed without the other one changing'); + } + }); + + const unsubscribe = combined.subscribe(v => { + values.push(v); + }); + + assert.deepEqual(values, ['a0b0']); + + c.set(2); + count.set(1); + + assert.deepEqual(values, ['a0b0', 'c2', 'a1b1']); + + unsubscribe(); + }); + it('derived dependency does not update and shared ancestor updates', () => { const root = writable({ a: 0, b: 0 }); const values = []; diff --git a/site/content/docs/04-run-time.md b/site/content/docs/04-run-time.md index 3b9097ae7464..d452a4b6b7a6 100644 --- a/site/content/docs/04-run-time.md +++ b/site/content/docs/04-run-time.md @@ -383,7 +383,7 @@ store = derived(a, callback: (a: any, set: (value: any) => void, update: (fn: an store = derived([a, ...b], callback: ([a: any, ...b: any[]]) => any) ``` ```js -store = derived([a, ...b], callback: ([a: any, ...b: any[]], set: (value: any) => void, update: (fn: any => any) => void) => void | () => void, initial_value: any) +store = derived([a, ...b], callback: ([a: any, ...b: any[]], set: (value: any) => void, update: (fn: any => any) => void, changed: boolean[]) => void | () => void, initial_value: any) ``` --- @@ -439,7 +439,7 @@ const tick = derived(frequency, ($frequency, set) => { --- -In both cases, an array of arguments can be passed as the first argument instead of a single store. +In both cases, an array of arguments can be passed as the first argument instead of a single store. In this case, the callback can optionally take a fourth argument, `changed`, which will be an array of Booleans describing which store value(s) changed since the last time the callback was called. (In the single-store case, the `changed` array would be pointless since it would always be equal to `[true]`.) ```js import { derived } from 'svelte/store'; @@ -449,6 +449,19 @@ const summed = derived([a, b], ([$a, $b]) => $a + $b); const delayed = derived([a, b], ([$a, $b], set) => { setTimeout(() => set($a + $b), 1000); }); + +const loggingSum = derived([a, b], ([$a, $b], set, _, changed) => { + const [aChanged, bChanged] = changed; + if (aChanged) console.log('New value of a', $a); + if (bChanged) console.log('New value of b', $b); + set($a + $b); +}); + +const complexLogic = derived([a, b], ([$a, $b], set, update, changed) => { + const [aChanged, bChanged] = changed; + if (aChanged) set($a + $b); + if (bChanged) update(n => n * 2 - $b); +}); ``` #### `get` From cf48c1bf3d0fcc4edb5b0d2815b479a77b50775f Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Mon, 29 May 2023 14:38:29 +0700 Subject: [PATCH 2/2] Ran Prettier to fix style issues --- packages/svelte/test/store/store.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/test/store/store.test.js b/packages/svelte/test/store/store.test.js index 7d5f18692224..0ccf8c3c4c53 100644 --- a/packages/svelte/test/store/store.test.js +++ b/packages/svelte/test/store/store.test.js @@ -379,11 +379,11 @@ describe('store', () => { const count = writable(0); const values = []; - const a = derived(count, $count => { + const a = derived(count, ($count) => { return 'a' + $count; }); - const b = derived(count, $count => { + const b = derived(count, ($count) => { return 'b' + $count; }); @@ -400,7 +400,7 @@ describe('store', () => { } }); - const unsubscribe = combined.subscribe(v => { + const unsubscribe = combined.subscribe((v) => { values.push(v); });