diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 755e9aab8583..635b6130e2e7 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -362,7 +362,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) ``` --- @@ -418,7 +418,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 stores 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'; @@ -428,6 +428,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` diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 51a13b85e21f..4b0fc55518b5 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -115,6 +115,20 @@ type Stores = Readable | [Readable, ...Array>] | Array = T extends Readable ? U : { [K in keyof T]: T[K] extends Readable ? U : never }; +/** + * Derived value store by synchronizing one or more readable stores and + * applying an aggregation function over its input values. + * + * @param stores - input stores + * @param fn - function callback that aggregates the values + * @param initial_value - when used asynchronously + */ + export function derived( + stores: S, + fn: (values: StoresValues, set: Subscriber, update: (fn: Updater) => void, changed: boolean[]) => Unsubscriber | void, + initial_value?: T +): Readable; + /** * Derived value store by synchronizing one or more readable stores and * applying an aggregation function over its input values. @@ -182,6 +196,7 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea const values = []; let pending = 0; + const changed = []; let cleanup = noop; const sync = () => { @@ -189,7 +204,8 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea 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 as T); } else { @@ -202,6 +218,7 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea (value) => { values[i] = value; pending &= ~(1 << i); + changed[i] = true; if (inited) { sync(); } diff --git a/test/store/index.ts b/test/store/index.ts index e49bf0572e08..38a73dc91425 100644 --- a/test/store/index.ts +++ b/test/store/index.ts @@ -358,6 +358,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 = [];