Hacker News new | ask | show | jobs
by rich_harris 1003 days ago
Yes, Vue and Solid — like Knockout — use a dependency tracking mechanism. Back in the day it was called `ko.observable`, nowadays we call them signals.

That's the part Knockout was right about. It's absolutely true that you can mishandle them and create a spaghetti mess, _if your design allows it_. Svelte 5 doesn't — it uses signals as an implementation detail, but in a way that prevents the sorts of headaches you're describing.

Signals and observables (e.g. RxJS) are related but separate concepts. The framework world is converging on signals as the appropriate mechanism for conveying reactivity — even Angular (which has historically been most closely tied to RxJS) is moving in this direction. I promise you it's not just a mass delusion.

5 comments

Not everyone has teams of perfect developers given all the time they want to make their perfect frontend app. Most of us deal with average teams where half the devs are at or below par while managing very tight deadlines. The spaghetti from everything in the whole system mutating always comes back to bite.

You call signals an "implementation detail", but if someone doesn't know that's what they are, then they'll wind up doing very bad things. This means they either aren't just an implementation detail or they are a hyper-leaky abstraction.

What I mean by 'implementation detail' is that you _literally can't get a reference to a signal_ in Svelte 5. This alone prevents people from mutating things in unexpected ways.
> You call signals an "implementation detail",

You haven’t even seen Svelte 5 yet, I don’t think it’s fair to say its internals will create trouble yet.

> Not everyone has teams of perfect developers given all the time they want to make their perfect frontend app.

Yes, but having gone through this at previous companies, the average/non-front end specialist developers definitely had an easier time understanding 2 way binding ala Knockout, MobX, Svelte, etc. The Redux style stuff was only pushed by the frontend brainiac types.

More like so easy to shoot themselves in the foot. React’s one-way data binding philosophy is defensive position against spaghetti code.
MobX with React is quite fine, haven't seen the problems you describe tbh
Pretext: I'm a backend developer that often needs to stare into the abyss that is frontend.

I used to say these same things about reactivity. It was highly confusing to me years ago; as a backend developer doing frontend tasks was always reaching outside of my cookie jar and testing the boundaries of what I knew vs what I thought I knew. Many headaches ensued. That said, using Vue 3 and Svelte 4 has made utilizing stores and reactivity a lot more obvious in my opinion. Things render when they're supposed to and my applications remain performant. As a non-frontend person, I mainly like the APIs that Vue and Svelte bring. In React I'm almost always switching to class based components for what I can do in the compositional API in Vue or what's natively possible in Svelte. That brings a lot of repeated code and doesn't look as clear to people not as familiar with React.

Seems like your comment cut off there, but if you're using class components in React, there will be repeated code, yes, which is why hooks were invented and why they enable you to reuse such code.
And lose most of the 'reactivity' since you'll now be doomed to manually track your own dependencies everywhere.
Can you explain why that is?

I find hooks are flexible enough that I tend to be able to implement more sane and manageable reactivity, not the opposite.

Maybe I’m misunderstand your issue, though.

I’m referring to the dependency array you pass into hooks, and any hook being able to trigger updates.

With classes there was no manual dependency tracking (except comparing old props?) and all re-renders were triggered by state, context or prop changes.

Not making a judgement either way, just stating the trade-offs.

> create a spaghetti mess, _if your design allows it_. Svelte 5 doesn't — it uses signals as an implementation detail, but in a way that prevents the sorts of headaches you're describing.

Care to elaborate or do you have a link to a blog post explaining further?

I'll try — I'm currently fielding a zillion messages so excuse brevity!

Basically, you can only modify state _where it's declared_. If you want to allow the 'outside world' to modify that state, you need to expose a function that does so. This is unlike cases where you're passing around an observable object where anyone with a reference, which they need for reading, can also write to it by doing `thing.value += 1`.

This is something Solid gets right — you can only change a signal's value if you have a reference to its setter.

> fielding a zillion messages

Surely you mean signals? :)

Thanks for the reply, Rich. It seems that, given a large enough codebase, that sort of spaghetti mess will emerge on its own, as not everyone will be so thorough. Granted, I haven't used runes and based on your blog post here, I'm not reading where it would prevent such headaches as there's not much mention of that there, so I don't know exactly how it'd work, but just based on my experience using things like Vue and Knockout in the past, it wasn't too clean.

I was just mentioning in another comment how Rust has a similar problem where, as in C, you can mutate variables via pointers anywhere in the codebase, but Rust counters that by having a borrow checker that explicitly tracks the changes made and makes sure that only one user can modify a variable at any one time. An analogous concept might be quite interesting to implement in all these signal based frameworks as well.

> An analogous concept might be quite interesting to implement in all these signal based frameworks as well.

Which is what Svelte 5 is doing with its compiler.

> […] given a large enough codebase, that sort of spaghetti mess will emerge on its own, as not everyone will be so thorough.

LOL! You've just described React projects! Yes, of course you and your team are wonderful and have all the FP experience, so obviously this isn't an issue for YOU.

Most React codebases however turn into big balls of mud. Enormous balls of useMemo-y, useEffect-y, re-render the world-y, spaghettified mud without even any compiler-enabled guardrails that get re-written every 18-24 months but "this time we'll do it right."

I think the true lesson here is that if you fundamentally dislike a programming paradigm, then everything written in it looks like a big ball of mud.
That's completely true. A concede the point.
Based on your profile and comments, it seems you have some particular grudge against React, so I'm not sure I can convince you of anything React related that you have not already convinced yourself of. Nevertheless, I (nor you, it seems) have not used Svelte 5 yet so I cannot judge, just that based on my, yes, personal experience, 2 way data binding based frameworks create messes. Even though you can shoot yourself in the foot in any language or framework, some are simply better at preventing it, such as C versus Rust, as I analogized.
I’m not expert in Svelte or React and have been following your conversation. You stress the point that two-way data binding creates mess. I tend to agree since I did my share of desktop programming long time ago. However, I don’t see how React solves the problem fundamentally. When you combine React with Redux or hooks IMHO you get exactly the same thing. The big difference is syntax that makes it easier to reason how view would re-render in response to change. But in a large enough project I can see how this became less and less true as distant modification trigger cascades of model updates/reducers/hooks and result in an unexpected behavior.
I always found observables need better nomenclature.

I know its not anyones fault (RxJS is amazing), but it does seem to invoke some gray area in the brain, thinking observables would do more heavy lifting around change tracking of inputs / sources, when its not that straightforward. They more rightly should be called SubscriptionStreams maybe

I think its always been somewhat poorly named, based on how often I've had to explain the concept to other developers.

Huh? It's called observable because it can be observed (that's what "-able" means). If it were actually meant to observe something (as you say), it would have been named "observer." But it's not - it's the callback that's the observer.

Also observables are more like streams than arrays.

I don't think I'm entirely out of band to say there could be a better way of naming them, like why not call them SubscribableStream.

Saying its an observable invokes a messy gray area in people's brains. I know this because I've had to explain observables to co-workers more times than I can count in my career (I'm a big fan of RxJS, I didn't mean what I said as a dismissal), and I like them for async work vs promises in many many cases, but the nomenclature does not invoke easy understanding

Because RxJS made at least one huge mistake: it made its observables lazy.

When does it start? How do you deal with oversubscription? Late starts? Double subscriptions?

On top of that streams are always a pain in the butt to debug.

The fifteen million obtuse methods/operators didn't help either (I see there are significantly fewer now).

And to remind you: it took the author of RxJava several months to understand the programming model: https://twitter.com/dmitriid/status/811561007504093184

Maybe it would be easier for you (and your colleagues) if the function to "observe" observables was called... observe, instead of "subscribe ?
not particularly, I think it would make it more confusing.

When I break it down to folks explaining its stream processing over iterables or async work and you subscribe (and can unsubscribe) a pipeline that runs over that stream, people get it quickly.

When I say you can "observe a stream" people start getting other connotations, for some reason. I have ran into this over and over and over. Smart people too, not talking about just juniors here

Indeed !

`observers` do exist in RxJs by the way: https://rxjs.dev/guide/observer

I think it is definitely a learning curve thing: when teaching/learning Observables it can help to use a name like EventStreams or PushStreams. (An RxJS "competitor" is named XStream for this among other reasons.) Observable is a simple English word and the meaning of the English word is complicatedly "shadowed" by what the tech means by it.

On the other hand, on the other side of the learning curve, Observable is a great name. It's a simple English word that you can say and write a million times (and you will need to when working with them) without extra PascalCase or things like that. Also, when you look at the overall "family" that Observables fit into, it is a name that fits the family: Iterable/AsyncIterable ("PullStreams") versus Observable ("PushStreams"). (In .NET LINQ the family uses Enumerable/AsyncEnumerable and also extends to Queryable and the strangely named but makes sense in context Qbservable. [QueryableObservable; often pronounced like "cube-servable".]) As a family of tools that all generally work well together and are essentially related in a "four quadrant" way, it helps that they all have a nice -able names that sound related.

Qt was always right.