-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a few few comments, but overall this looks great - thank you!
} | ||
}; | ||
|
||
window.addEventListener('hashchange', callSubscriptions); |
There was a problem hiding this comment.
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 subscriptions = []; | ||
|
||
let lastHash; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Let's consider what we want the store contract code example to illustrate. I wanted to find an example that would not be too complicated, so it could focus on the essentials of implementing the contract rather than the getting bogged down in the specifics of the example. While it would certainly be more diligent to add and remove the const hashStore = {
subscribe(subscription) {
subscription(location.hash); // Call subscription with current value
if (subscriptions.length === 0) {
// On the first subscription, set up a `hashchange` event listener,
// to keep up with browser history or other external hash changes
window.addEventListener('hashchange', callSubscriptions);
}
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
if (subscriptions.length === 0) {
// When no subscriptions are left, remove the `hashchange` listener
window.removeEventListener('hashchange', callSubscriptions);
}
}
};
},
// ...
}; I'm not necessarily against it, but I think it might be more valuable to have a clear and general example than a perfect implementation of a hash store. It works the same either way, if someone copy/pastes the snippet to play around with it. In the same spirit, I want to follow up on my reply to your comment about the How important is the requirement that subscriptions be called synchronously? The store works without it, and it would allow us to skip the How important is it that you can call I haven't found an elegant and reliable way to only react to events when they're not triggered by the |
Maybe this is an OK solution: /* Example: a custom writable store for `location.hash` */
const subscriptions = [];
let storeValue = location.hash; // Set initial value
const hashStore = {
subscribe(subscription) {
subscription(storeValue); // 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) {
storeValue = hash; // Set the value
location.hash = hash; // Update location.hash
for (const subscription of subscriptions) {
// Call all subscriptions
subscription(storeValue);
}
}
};
// Event listener to stay in sync with external hash changes
window.addEventListener('hashchange', () => {
if (location.hash !== storeValue) {
hashStore.set(location.hash);
}
}); I think that's a very clear example, at the expense of not adding/removing the event listener as needed. |
Hopefully an even clearer example, which allows explicitly setting the the store multiple times and notify subscriptions again.
Since sets have a built in `.delete` method, we can avoid the gnarly `.splice` stuff in the unsubscribe function.
Not sure exactly what the syntax of these signatures are, but they look like TypeScript, so that's what I went with for the object literal too.
Why not using a Set instead of Array for subscriptions ? Adding a subscription
with subscriptions.add(subscription) remove a subscription const index = subscriptions.indexOf(subscription);
if (index !== -1) {
subscriptions.splice(index, 1);
} with subscriptions.delete(subscription) set value of subscriptions for (const subscription of subscriptions) {
subscription(storeValue);
} with subscriptions.forEach(subscription => subscription(storeValue)); |
I am kind of thinking now that the API docs are the wrong place to jam in a whole custom store implementation. The rest of the changes look great though, and we could get them in a lot faster if we don't have to worry about figuring out the example. You had mentioned in chat about maybe making the existing custom store example more helpful and linking to that. I don't think that's a bad idea, but I do think there are two different 'custom store' concepts we ought to be conveying. One is a store based on the exports from tl;dr - If you don't mind, I'd like to just remove the example for now so we can get this in, and think more about the example or examples later. |
@Conduitry Of course! Let's continue discussing it in an issue. I don't know if you noticed that I add a little type signature (0193422). It might be enough extra help for others who want to take a stab at making their own stores from scratch. |
This is gold. And it's hardly available. Would have been nice, to not bloat the docs, is to have a link that will show/hide content. So if you click on "Show example of a store contract implementation", this code expands. |
I've been scratching my head trying to understand the ins and outs of the store contract, and how to implement custom stores. This PR is a suggestion for how to improve the documentation.
svelte/store
intro to be much more explicit about the store contract documentation.01-component-format.md
to describe the syntax first (which is what the headline promises) and the contract second.location.hash
and thehashchange
event in a custom store. I'm not sure if this is the best example, but it is small. It might be better to include an example of, say, RxJS, but I'm probably not the right person to write it.