Comments (5)
With multiple
and dispose
both being optional arguments, I think the protocol itself answers this question, no we don't need them.
Without them, a Provider is free to deliver any of the more complex values that you've defined here. Though it does become the responsibility of the Provider to document those complexities and then Consumer to actively manage those complexities.
With them, even simple data types can be kept up-to-date without the intervention of a second management context. This means that if you choose to use multiple
and dispose
then the value can always be assumed to be update over time without knowing anything about how to leverage that data.
This flexibility feels like a good balance of capability and API options without being overly strict. In that way, there are even more reactivity models that a Provider can support and they are still able to choose which one they are willing to support.
from community-protocols.
With multiple and dispose both being optional arguments, I think the protocol itself answers this question, no we don't need them.
Though it does become the responsibility of the Provider to document those complexities and then Consumer to actively manage those complexities.
I think these are the key points and which directly conflict with each other. dispose()
is not optional in the current proposal because any consumer needs to defensively support it just in case the provider requires it.
Of course in an application with two cooperating components maintained in the same codebase with the same ownership, devs can short-cut this process knowing that no, the provider really doesn't require dispose()
. But especially in the context of a community protocol, the loose coupling between provider and consumer means that they can't make any assumptions about each other, including whether or not the value should be disposed.
Here's a related question to frame the conversation: Consider a library with a provider component shipped to users and working successfully. This provider does not send a dispose()
function because it just isn't necessary. Then one day, I make a change which now requires cleanup, so I add a dispose()
function and release a new version. Is this change:
- A breaking change because consumers were not expected to implement
dispose()
previously and now they are. - A non-breaking change because consumers were expected to defensively implement a previously unused
dispose()
, and since they didn't they have a bad consumer which broke the protocol.
As the protocol is currently written I feel like 2. is at least somewhat implied, but in practice in such a situation I'd probably go with 1. just to avoid angering users. Of course as a provider, I'd probably just not use dispose()
and instead use my own reactivity primitive specifically to avoid this. But the question still remains for consumers which don't have luxury.
Every situation is different and we don't necessarily need to decide on a "correct" answer on breaking changed here, but I think it demonstrates the "need" for dispose()
as the protocol is currently written and why there might be value in removing it.
from community-protocols.
The reason why multiple
and dispose()
are needed is to reduce memory overhead when a provider can deliver new values. If the consumer is never going to use updated values, then the provider doesn't need to keep a reference to the context-request event's callback. So providers should only store the callback if multiple
is true. Then if the consumer no longer needs the values, like when it's disconnected, it should call dispose()
to tell the provider to release the reference to the callback.
Doing this releasing easily and ergonomically is critical because if a provider holds on to a callback when it shouldn't, and that callback closure has references into the consumer element (almost all will), then the provider will hold on to consumer elements, which could be a serious memory leak.
The nice thing about this subscribe/dispose protocol is that it's very easily abstracted into helpers that do the right thing on host element disconnection to avoid those leaks. The @lit-labs/context
package automatically hooks disconnectedCallback()
to call dispose()
. I think that kind of DX is very important to ensure correct usage.
The problem with other "subscription" techniques:
- Promises are async only and one-shot only.
- Arrays don't notify of new values
- Generators require a lot of ceremony to get the same behavior, and probably in fact need the same
multiple
/dispose()
API. A provider can't have a single generator per value, because only one consumer would get each value. So it needs to maintain a generator per consumer. But then it needs to know when to release that generator, so it needs adispose()
mechanism. - EventTargets are ok, but this protocol already uses Events a lot, they're more heavyweight than function calls, and we need to watch out for performance here. You also would need to call
removeEventLister()
very similarly todispose()
. - Subscribables - I don't know what these are. I don't think there's anything by that name in JS or DOM?
- Signals - same.
from community-protocols.
The problem with other "subscription" techniques:
I'm not advocating for any of these as a coon dispose mechanism. Only using them to illustrate the options snd trade offs a provider can choose to use. The protocol itself wouldn't specify any of these, just like it doesn't today.
Subscribables - I don't know what these are. I don't think there's anything by that name in JS or DOM?
I was referring to the interface for consuming Obervables
without an RxJS dependency.
https://rxjs.dev/api/index/interface/Subscribable
Signals - same.
https://www.solidjs.com/tutorial/introduction_signals
Again, not saying the protocol should use any of these, just that providers can choose what works for them.
The nice thing about this subscribe/dispose protocol is that it's very easily abstracted into helpers that do the right thing on host element disconnection to avoid those leaks. The
@lit-labs/context
package automatically hooksdisconnectedCallback()
to calldispose()
. I think that kind of DX is very important to ensure correct usage.
That is a good point. I can see how a standard dispose semantic would be useful for component libraries to implement implicitly so devs don't have to think about it as much. If the dispose semantics are unique to each context and the exact reactivity primitive it choose, then the component library can't do much to abstract away that complexity.
I think for that reason alone I'm convinced that we should have a common dispose mechanism. Apologies for any noise here, it's just helpful to explicitly call out motivations like this sometimes.
Sidebar: Lit's implementation is eerily similar to one I was working on which motivated this issue.
from community-protocols.
All good points raised, and honestly I went back and forth a number of times on the value of these arguments. In the end it just came down to keeping the protocol as flexible as possible without putting undue burden on implementors. Glad this made sense to more folks than just me!
from community-protocols.
Related Issues (20)
- [meta] Add guidance on events and interfaces HOT 2
- [pending-task] Should PendingTaskEvent have a `type` field? HOT 2
- [defer-hydration] Controlling hydration with the "defer-hydration" attribute
- Runtime agnostic web components HOT 5
- Proposal: Well-known symbol for DOM shim HOT 1
- [context] are context objects needed? HOT 36
- [context] do providers retain references to consuming components? HOT 11
- [context] improve naming of dispose and multiple arguments HOT 1
- [context] supporting cases where provider is defined after consumer HOT 11
- Proposal: Reactive controllers protocol HOT 4
- Support versioning of Web Components HOT 4
- [progressive-hydration] conditions for first level hydration timing HOT 12
- [pending-task] Cancellation mechanism HOT 1
- [progressive hydration] self hydrating custom elements HOT 4
- Compatibility and Interop Specification (Versioning WCs) HOT 4
- [defer-hydration] Requesting hydration of a disconnected component HOT 7
- [context] Fully event driven context protocol HOT 7
- [context] Event namespace HOT 8
- IndexedDB Observer / Event protocol HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from community-protocols.