-
Notifications
You must be signed in to change notification settings - Fork 63
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
Refactor Signal Class Hierarchy for Improved Type Safety and Extensibility #230
Comments
I can't speak for everyone here, but I'm a big fan of this proposed type change. 🎉 I think we have some subclassing tests in the polyfill (but if we don't, we should add them). Could be worth a PR to try it out and see what happens and then post the results back here? |
I think the question is: what happens if you subclass I'm trying to remember what other precedents there are in the standard. The base IMO this may be a reason to come up with some more general thing that's interoperable with signals and which State and Computed are special cases of (with a more convenient API and likely their own tuning in implementations), but absent the capability to subclass or instantiate |
If the main use case is for a consumer to check whether an object they're holding is a genuine signal, it's probably better to solve that directly, if for no other reason than to improve forward compatibility - if new classes implementing
Or you could do both, why not? Either way, a pretty basic implementation would look a lot like this (as expressed in TypeScript): namespace Signal {
[Symbol.hasInstance](value: any): value is Signal<T> {
return value instanceof Signal.State || value instanceof Signal.Computed;
}
} Although that's not quite how I would spec it, and most engine implementers would probably do something else entirely. |
@kcastellino This would work, but there is one problem on the type side. Typescript doesn't support symbol properties on namespaces yet (microsoft/TypeScript#36813). So interface Signal<T> {}
declare namespace Signal {
interface State<T> extends Signal<T> {}
interface Computed<T> extends Signal<T> {}
}
declare const Signal: {
[Symbol.hasInstance](value: unknown): value is Signal<unknown>;
State: { new <T>(): Signal.State<T> };
Computed: { new <T>(): Signal.Computed<T> };
}; |
TypeScript can just use a value with the That workaround is probably also why they haven't fixed that issue yet. Otherwise, they wouldn't have been able to type a sizable chunk of decade-old libraries, ranging from Mithril to jQuery to Glob. |
The more I think about this, the more I think using Because of this, I feel that since checking whether an object is a runtime-optimised signal is similar to checking whether an object is a runtime-optimised array, a |
Currently,
State
andComputed
are defined as standalone classes within theSignal
namespace:I suggest a refactor where
State
andComputed
extend a baseSignal
class:With this structure, we can perform type checks using a single base class:
This is more straightforward and efficient than building a custom type guard function like:
The base
Signal
class can beread-only
, whileState
, extendingSignal
, can expose theset()
method, andComputed
canset()
the signal internally. So when I saysignalOrValue instanceof Signal
I don't care about if it's aState
orComputed
, i just know its something that can change and I can watch those changes.Of course I might be missing something here, in that case I would like to know why?
The text was updated successfully, but these errors were encountered: