Skip to content

Clearer documentation of store contract #4216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
66 changes: 48 additions & 18 deletions site/content/docs/01-component-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,24 +147,7 @@ If a statement consists entirely of an assignment to an undeclared variable, Sve

---

A *store* is any object that allows reactive access to a value via a simple *store contract*.

The [`svelte/store` module](docs#svelte_store) contains minimal store implementations which fulfil this contract. You can use these as the basis for your own stores, or you can implement your stores from scratch.

A store must contain a `.subscribe` method, which must accept as its argument a subscription function. This subscription function must be immediately and synchronously called with the store's current value upon calling `.subscribe`. All of a store's active subscription functions must later be synchronously called whenever the store's value changes. The `.subscribe` method must also return an unsubscription function. Calling an unsubscription function must stop its subscription, and its corresponding subscription function must not be called again by the store.

A store may optionally contain a `.set` method, which must accept as its argument a new value for the store, and which synchronously calls all of the store's active subscription functions. Such a store is called a *writable store*.

```js
const unsubscribe = store.subscribe(value => {
console.log(value);
}); // logs `value`

// later...
unsubscribe();
```

---
A *store* is an object that allows reactive access to a value via a simple *store contract*. The [`svelte/store` module](docs#svelte_store) contains minimal store implementations which fulfil this contract.

Any time you have a reference to a store, you can access its value inside a component by prefixing it with the `$` character. This causes Svelte to declare the prefixed variable, and set up a store subscription that will be unsubscribed when appropriate.

Expand All @@ -189,6 +172,53 @@ Local variables (that do not represent store values) must *not* have a `$` prefi
</script>
```

---

You can create your own stores without relying on [`svelte/store`](docs#svelte_store), by implementing the **store contract**:

1) A store must contain a `.subscribe` method, which must accept as its argument a subscription function. This subscription function must be immediately and synchronously called with the store's current value upon calling `.subscribe`. All of a store's active subscription functions must later be synchronously called whenever the store's value changes.
2) The `.subscribe` method must return an unsubscribe function. Calling an unsubscribe function must stop its subscription, and its corresponding subscription function must not be called again by the store.
3) A store may *optionally* contain a `.set` method, which must accept as its argument a new value for the store, and which synchronously calls all of the store's active subscription functions. Such a store is called a *writable store*.

```js

/* Example: a custom writable store for `location.hash` */

const subscriptions = [];

let lastHash;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What specifically is this trying to address? I think the built-in writable stores will call subscribers again if you .set them to their current value, so it might be a bit confusing if this custom store didn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting location.hash triggers the hashchange event asynchronously, so without this check every .set() would call the subscribers twice.


const callSubscriptions = () => {
if (location.hash !== lastHash) {
lastHash = location.hash;
for (const subscription of subscriptions) {
subscription(location.hash);
}
}
};

window.addEventListener('hashchange', callSubscriptions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more in the Svelte-y spirit to have this store not actually register the hashchange event handler until the first subscriber is added, and to unregister it when the last subscriber is removed. This can be easily done by checking subscriptions.length before the push and after the splice.


const hashStore = {
subscribe(subscription) {
subscription(location.hash); // Call subscription with current value
subscriptions.push(subscription); // Subscribe to future values
return () => { // Return an "unsubscribe" function
const index = subscriptions.indexOf(subscription);
if (index !== -1) {
subscriptions.splice(index, 1); // Remove the subscription
}
};
},
set(hash) {
location.hash = hash; // Set the value
callSubscriptions(); // Synchronously notify all subscriptions
}
};


```


### &lt;script context="module"&gt;

Expand Down
6 changes: 5 additions & 1 deletion site/content/docs/03-run-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ Events dispatched from child components can be listened to in their parent. Any

### `svelte/store`

The `svelte/store` module exports functions for creating [stores](docs#4_Prefix_stores_with_$_to_access_their_values).
The `svelte/store` module exports functions for creating [readable](docs#readable), [writable](docs#writable) and [derived](docs#derived) stores.

Keep in mind that you don't *have* to use these functions to enjoy the [reactive `$store` syntax](docs#4_Prefix_stores_with_$_to_access_their_values) in your components. Any object that correctly implements `subscribe`/`unsubscribe` and (optionally) `set` is a valid store, and will work both with the special syntax, and with Svelte's built in [`derived` stores](docs#derived).

This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](docs#4_Prefix_stores_with_$_to_access_their_values) to see what a correct implementation looks like.

#### `writable`

Expand Down