-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Support trailing $ name convention for stores (Observables) #6373
Comments
Interop with other ecosystems or no, this would be a breaking change. |
I'm not aware of how this would be a breaking change but I'll take your word for it. Could you elaborate? My primary concern is that the An example that just came up, when refactoring a reactive variable to a shared store: From: <script>
let amount = 0
</script>
You have {amount} comments. Currently, one has to also change the reading of the value to <script>
import { amount } from './shared/amount'
</script>
You have {$amount} comments. With the <script>
import { amount$ } from './shared/amount$'
</script>
You have {amount} comments. |
It's a breaking change because everyone using the |
To your point there is a breaking change with the current compiler rule, where
That's a good consideration. The compiler would need to do look for variables using something like So the difference would be not only the match, but what drives the match. For the Would you agree with that assessment? I don't know if the ergonomics of the I'm playing around with different naming conventions & patterns. If anybody has suggestions or wants to continue the discussion, I would appreciate it. |
Not gonna lie, this is a bit of a lightbulb moment. I just wish we'd had it two and a half years ago! The more idiomatic RxJS interop is cool, but I'm much more persuaded by the refactoring argument. It's something I've run into on occasion, and it's a a legitimate criticism people have made of Svelte's reactivity model, wherein stores are perceived to be second-class citizens compared to component-local state (or props). I'm also persuaded by the thought that Two points:
So the question is 'is this change of sufficient value that it's worth the extreme disruption it would cause?' I suspect most people here would say 'no', but I also think there's at least a possibility that it is, insofar as it would fix one of Svelte's (arguably) most noticeable warts. Having said all that, the main frustration I encounter when dealing with stores is the requirement that they be free variables rather than (e.g.) object properties. Some code from a project I'm currently working on: export let runner;
const weight_idle = spring();
const weight_running = spring();
const weight_celebrating = spring();
$: weight_idle.set($runner.state === 'idle' ? 1 : 0);
$: weight_running.set($runner.state === 'running' ? 1 : 0);
$: weight_celebrating.set($runner.state === 'celebrating' ? 1 : 0); Having three variables for each of those weights, rather than a single object or map with export let runner$;
const weights = {};
states.forEach(state => weights[state + '$'] = spring());
$: states.forEach(state => {
weights[state + '$'].set(runner.state === state ? 1 : 0);
});
// later
$: angle = weights.idle * angles.idle + weights.running * angles.running; Since the compiler can't realistically know which properties of So if we're discussing the possibility (however remote!) of changing store syntax, I'd love for us to see if we can come up with an approach that makes it possible to solve this problem at the same time. |
FWIW, I have deliberately written |
Thank you for the discussion. I have some good news to report. I have been rolling out the
The With that in mind, I'm not too concerned about changing the syntax, as long as To keep the <script>
import { value$ } from './value$'
value: $value$
</script>
<input bind:value> Another reason to not change the syntax is that sometimes it's useful to have an <script lang=ts>
import { value$ } from './value$'
export value:string
value: $value$
</script>
<input bind:value> @Rich-Harris Correct me if I'm wrong but, would the bottom block not work in svelte 3 as long as export let runner$;
const weights = {};
states.forEach(state => weights[state + '$'] = spring());
$: $runner$, states.forEach(state => {
weights[state + '$'].set($runner$.state === state ? 1 : 0);
});
// later
$: angle = weights.idle * angles.idle + weights.running * angles.running; https://twitter.com/wycats/status/1380386140478271488 Yehuda Katz had an interesting insight into composing reactive cell primitives (i.e. svelte stores). I'm looking for ways to ergonomically compose svelte stores into objects as well. I have started to add the const val$ = writable$('')
val$._ = 'new value`
console.info(val$._) It would be interesting to compose svelte stores into objects. For my purposes, mainly in Controller logic, having one of the above defined stores assigned to a prop works great. There's no need for decorators & reading/writing values from stores can occur inline. A decorator could be used on the Class as syntax sugar for the get/set proxy property to the store well. I'm not particularly interested in using decorators & proxy get/set props at this time, but Yehuda, Ember, MobX, Angular, & others use decorators & proxy get/set props. In Svelte components, there are also cases when calling the get/set props is necessary, such as in functions or reading the store value without using |
@ryansolid has astute commentary on stores as well. He addresses the trailing $ under the "Compilation Shortcomings" section. https://itnext.io/designing-solidjs-reactivity-75180a4c74b4 I like using the trailing Nonetheless, Solidjs is impressive & I share many of Ryan's sentiments including the observation that the local component scopes tend to not have as much of the app logic in more complex systems. I find that I'm using stores in components significantly more than reactive variables. His performance concerns are also interesting, since large apps have many components, so having a way to "inline" the components on compilation would be beneficial. |
Leaving this here for possible later use, if this is implemented. This is how to get the TS types: type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }
type StoreValue<T> = T extends SvelteStore<infer Value> ? Value : never;
type WithStoreValues<Object> = Object & {
[Property in keyof Object as Property extends `${infer Key}$` ? Key : never]: StoreValue<Object[Property]>;
};
type Works = WithStoreValues<{a: true; b$: { subscribe: (run: (value: boolean) => any, invalidate?: any) => any } }>; |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Further activity to prevent stale-bot from closing a proposal on which no decision has yet been made. |
Not sure this adds more to the conversation but I've run across the problem in my own toy lib https://github.com/bradphelan/immer.loves.svelte The simple example is <script lang="ts">
type Data {
readonly a:string
readonly b:string
}
// declare the root store
let store:Writable<Data>
// declare subStores as projections onto the root store
let aStore:Writable<string> = subStore(store,s=>s.a)
let bStore:Writable<string> = subStore(store,s=>s.b)
</script>
<input type="text" bind:Value={$aStore}/>
<input type="text" bind:Value={$bStore}/> requires creating those pesky local variables <script lang="ts">
type Data {
readonly a:string
readonly b:string
}
let store:Writable<Data>
</script>
<input type="text" bind:Value={subStore(store,s=>s.a)$}/>
<input type="text" bind:Value={subStore(store,s=>s.b)$}/> |
Closing as won't do - Svelte 5 introduces runes, which replace stores in most occurences and allow for fine grained reactivity (which was one of the reasons for wanting nested subscriptions); as such we're not gonna change its subscription syntax. |
The RxJS community has adopted trailing
$
for an observable name. Svelte supports leading$
is naming the value of the store (observable).It strikes me that the trailing
$
on the store would be more consistent with standard javascript naming practices, especially when working with large codebase having helper functions.Svelte
RxJS
Yes, the svelte example has less code & is easier to read, however it can be improved by also supporting trailing
$
.Helper functions in javascript modules
Creating a function that takes a store or a value of a store as an argument, the programmer is faced with a quandary. What name should the value variable be?
Should it be
amount
or$amount
? In normal javascript & with observables having the trailing$
, the name of the value would beamount
. With the value having the$
prefix, the name of the value is$amount
. However, this does not work well when passing the value as a property of an object. Should all props also be named$amount
? What about api calls? As some point, the nameamount
will be required to reperesent the value, which contradicts svelte's naming convention.Solution
Support both the prefix
$
for values and the suffix$
for stores. Both naming conventions can be supported by the svelte compiler.This will fix naming collision by making the store have the
$
suffix. The store is not passed data & if it is, would follow the convention set by RxJS. Also the additive effect of the$
suffix implies that the observable characteristic is wrapping the value, instead of infering from the store.Current implementation of svelte
The
$
suffix can be added to the store, but the$
prefix of the value is still required.Adding support for the
$
store suffix stripping off the$
would fix the naming issues of support functions on the value of the store.The text was updated successfully, but these errors were encountered: