Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions site/content/docs/03-run-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

---
Expand Down Expand Up @@ -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';
Expand All @@ -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`
Expand Down
19 changes: 18 additions & 1 deletion src/runtime/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ type Stores = Readable<any> | [Readable<any>, ...Array<Readable<any>>] | Array<R
type StoresValues<T> = T extends Readable<infer U> ? U :
{ [K in keyof T]: T[K] extends Readable<infer U> ? 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<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>, set: Subscriber<T>, update: (fn: Updater<T>) => void, changed: boolean[]) => Unsubscriber | void,
initial_value?: T
): Readable<T>;

/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
Expand Down Expand Up @@ -182,14 +196,16 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
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 as T);
} else {
Expand All @@ -202,6 +218,7 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
(value) => {
values[i] = value;
pending &= ~(1 << i);
changed[i] = true;
if (inited) {
sync();
}
Expand Down
39 changes: 39 additions & 0 deletions test/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down