I'm a big fan of Svelte. I've raved about their documentation before, but it bears repeating: this should be the gold standard. You can read it all in a day. There are examples to follow right next to the documentation.
Svelte is both succinct and powerful. I find this in contrast to React which is often baffling and incoherent. I say this as someone that has used React professionally for 6 years. They've changed their mind at least 3 times on the API and none of it fits together today. Hooks and classes don't mix, lifecycle methods are still out there and just as confusing today as they were years ago. Mixing state and functions. It's just a horrible bag of half-baked bad ideas. And it's not even batteries included.
Svelte has it all built-in. It has the equivalent of CSS modules. It has global reactive state and an incredibly simple state protocol that seems almost stupid if you're coming from Redux. You step out of your daze and realize you don't need all the rituals and boilerplate. The things I've seen built on top of that simple protocol can be incredible. It's not magic. It's just JavaScript.
I dare say that Svelte is a joy. But I hesitate to even leave this comment here. Because I know the architecture astronauts are listening, eager to sink their claws into this simple elegant new thing and fill up their Github profiles with soon-to-be abandonware cruft which they will use to pad out article after Medium article which will all pour down upon Hacker News like a shit typhoon. Prodding us to join their cult of Svelte+RxJS. Or Svelte Sagas. Or their random dogma they toss into a giant bucket labeled "Svelte best practices." And, of course, "Ten ways you are doing Svelte wrong."
But I wish new frameworks stayed away from inventing new language syntax. Not only is it a cognitive burden on programmers, it throws all the tooling off.
From Svelte: {#each cats as { id, name }, i}
JavaScript already has facility for loops and mapping, and relying on that will automatically get you tooling/IDE support. React handles this aspect quite well, and to me that's a point in favor of React.
I do agree with your post - especially on hooks being a step backward.
I agree that gratuitous syntax differences are tiring, but in this case, there's actually a very good reason why it's not just using JS. When it comes to reactive systems, you need to be able to statically analyze control flow structures in order to compile to efficient code. In JS, a for loop is a loop, but so is array.map, Object.keys and someLib.each. The loop could be hidden three layers deep in a higher-order function composition from some third party library, it could be tail call recursion, etc. Dealing with this sort of complexity when writing optimizing compilers is very very hard, compared to coming up with dedicated loop syntax, because at any point it's possible there's a value of polymorphic type, a Proxy, a getter/setter side effect or some other crazy JS shenanigan going on.
This is why, for example, Solid.js does control flow through <For /> and <Show /> components instead of map/ternaries even though it uses JSX.
Until you try to do something svelte doesn't understand. Then it'd silently break. Templates are powerful _because_ of the restrictions they place, not in spite of them.
I think a much better recommendation would be an existing template language, iff svelte could understand it with perfect parity.
I hate being handed normal syntax to use when not all of it is normal, or when using it has different semantics than the syntax usually carries.
I was saying elsewhere on the thread - I am not suggesting that each should be turned into a runtime call. Instead, 'svelte.each()' callsites are easy to identify in the AST and transform in the compiler.
The danger is that a developer will use `svelte.each()`, thinking it uses the same rules as normal JS syntax, but it turns out Svelte applies its own rules which _are_ slightly different (hypothetically).
So the developer must remember a new rule: "JS rules when calling foo.each(), Svelte rules when calling svelte.each()". A little harder to remember. Having a unique non-JS syntax makes the distinction obvious.
That's a pretty reasonable argument and I believe a big part of what makes hooks somewhat complex since they look like regular Javascript but can behave differently.
FWIW, I did a mini-study a while back about the different possible syntaxes. It turns out there's more than meets the eye wrt what control flow structures need to do, and there are also DX reasons why the dedicated syntax might be more desirable: when you consider all permutations of loop flavors - w/ else clauses, keyed, exposing index, etc - the DSL consistently came out on top in terms of succinctness and readability (for an average developer familiar w/ algol-like syntax).
There are also technical reasons why `svelte.each` would be problematic, namely the fact that it is a compile-time concept which does not necessarily compile correctly if you move the call into a library, unless you have an extremely sophisticated compiler (aka: a slow compiler)
> There are also technical reasons why `svelte.each` would be problematic...
I am not suggesting that the svelte.each() call be moved into a library; I'm proposing that the callsite is trivial to identify in the AST. Svelte compiler could then do the same thing that it does now.
Yes, parsing is trivial, but compiling (in the traditional sense, not the transpilation sense) is a different story. The other sibling comment nails it.
Grammar enforces where a grammar construct is allowed to exist. A custom grammar can be very restrictive (which it is, to great effect, in Svelte's case).
If we reuse JS grammar, then that comes with expectations of what should semantically work. For example something as simple as `$ = svelte.each` might evade a not-sufficiently-smart compiler. Or maybe `console.log(svelte.each(...))` might mis-compile because nobody ever thought to handle that case.
Having used systems with compile-time grammar constructs that look like runtime constructs, I can tell you that having your expectations about semantics being changed from under your feet can be a very frustrating experience.
You're right about it being complex (my experience [1]), but such work is within scope for what's perhaps a central design decision for a library. Yes there'll be bugs, but it'll stabilize over time.
Just to be clear, I am NOT saying that Svelte needed to do this. But merely suggesting options for discussion and learning.
To deal with the issues they're talking about, you usually end up encumbering `svelte.each` with limitations such that it only looks like Javascript superficially.
In such a case, I think it's less confusing at the end of the day to just have a custom DSL for looping so that there's no clashing with its list of arbitrary limitations.
I think you are persistently suggesting something to someone who knows more than you, without taking the proper time beforehand to understand the problem space.
I do believe I understand the problem space well enough to comment on it - both AST analysis and frontend frameworks. My comment above also included a clarification that I was not suggesting that this be turned into a runtime call. Just presenting an entirely subjective viewpoint (of avoiding new language syntax), but one that many people share.
Javascript was originally supposed to be a Lisp. In this alternative universe, there would be no need for Svelte. For a portal into this universe, see Clojurescript's Mr. Clean [0].
I was with you on this topic, and I have rants about this elsewhere.
But with Svelte 3 I actually agree with their choice now:
1. The extra templating syntax is rather small and concise. And explicit.
- There are very few directives, and they follow the same rules: `{# start block }{: alternative branch}{/end block}`
- external data that comes from Javascript is always `{data}`
- properties of html components that can be bound are always `prop:name|modifiers={data}`
This is in contrast to, say, Vue, where the syntax can never decide where it's going.
And compared to React, this isn't Javascript either if we think about it:
{
svelte.each(cats, ({ id, name }) => <p>Hello {name}</p>)
}
That's a specific extension that compiles to Javascript
2. Your work becomes MVC-ish
Your template/html becomes "I have some data, let's display it". And since the template syntax doesn't allow arbitrary logic, you end up collocating transformations of your data with the code that's fetching it. Instead of doing some weird logic inside the template like `data.map.filter.map`
3. But to each their own :)
What really turned me towards Svelte is going through the tutorial and then trying it. It really is good :)
As I noted in another comment, what we’re really dealing with in compiled templates are reader macros. And unfortunately, reader macros break composability.
I think many people adopted React because Facebook was behind it, and using it in production, the ecosystem is huge, there is a react- package for everything you can image, there is also react-native, I don't like it, but there it is, you can reuse the knowledge to build native mobile apps.
Now Svelte, I haven't been following, but I think the creators are working in what's going to be next and even better framework[1], to your point of things in Svelte not changing as much as in React, maybe it just a matter of time.
Here is the video I had in my bookmarks for a while, I still couldn't make the time to watch, so I hope my comment is not too far from reality.
It's so boringly cynical to suggest that people use something because they're lemmings rather than any of the great reasons to use something.
Go watch the original React debut talk and ask yourself if React had/has anything going for it beyond Facebook's backing. I know I, and most developers I know, dropped Backbone/Knockout/whatever instantly to use React because it was so obviously better.
Now that unidirectional flow is the state of the art, the difference between the main frameworks just isn't that big. You gain a few things here, lose a few things there, and write a solution that is similar to what you would have written with the other frameworks.
> I know I, and most developers I know, dropped Backbone/Knockout/whatever instantly to use React because it was so obviously better.
You skipped a huge step. There was angular before react. Which was horribly bloated and over complicated. People wanted something simpler. React, with it's facebook clout won in the end.
Both the uni-directional data flow and virtual dom were not exactly new ideas when React/Flow came out. There were other libraries/frameworks playing with similar concepts at the time. Half a dozen new ones appeared in 2014. React won the lion's share of developer adoption.
I personally heard that 'objectively better' line incessantly from 2015 onwards, with 'it has the largest community' always being a top highlight. Facebook's backing and the celebrities that developed in the JS community around the time certainly played a part in it.
I went to one of FB's early internal meetups about React and I was initially skeptical of JSX but very excited by what it offered.
I was working at Groupon and using Backbone at the time and found nesting components to be a major hassle. We used a framework called Thorax on top of Backbone, which helped but still fell far short of React's convenience.
> to suggest that people use something because they're lemmings
That's not what I intended to say, of course that's not the only reason people choose React, but I think they are valid reasons to help you decide.
For example let's say two libraries are technically different but equivalent productive and nifty, choosing the one that has a bigger ecosystem is a totally valid choice.
Many people had to sell React to their bosses, and the fact that there was a presentation by Facebook, helped a lot, I don't see how stating the obvious is calling lemmings to anyone.
At the end of the day, it depends on your goals and motivations, I can't talk for everyone, I was actually saying some of the reasons that helped me decide on choosing React.
I don’t think any of your arguments in favour of react are sufficient. JQuery had a huge number of libraries and could be used to make websites that worked. Angular was backed by Google who generally have more clout than Facebook. I think most comparisons would find that jQuery does not solve the same problems and that angular resembles the kind of steaming brown thing one might find behind a cow—and that’s before you consider backwards compatibility.
I would argue that react has some particular merits (for three the whole vdom based architecture, using jsx, and unidirectional data flow), that it was the first (widely known) framework with those merits, and that it has generally been backwards compatible or only required simple changes. I think these do a better job of explaining why react became popular and inertia explains why it is still popular.
Your counter points don’t check out too much though?
> jQuery
Just not possible to compose, which makes full applications difficult. No answers for state management or such either, completely a different scope than frameworks
> Google more clout than FB
In some domains sure, but I’ve never understood how people don’t think FB are masters of UI. There is probably no org with more UI clout than FB, they built their own insanely adopted library after all while Google can barely force their own engineers to use Angular
React is also unquestionably the most JavaScript of the libraries, a component’s markup is such a thin abstraction around an object and maps perfectly to DOM trees and concepts, classes or functions or hooks are all using native JS concepts, explicitly typed typescript and react genuinely eliminates the need for all simple testing plus makes for a streamlined developer experience, and its concepts of lifecycles and composability introduce complexity but in my opinion in a clearly delineated and extremely powerful way
Also, as always I have to note this, the churn argument is completely overblown wrt React, each way of writing code still works or compiles, conceptually the lifecycle and state management never changed just its syntax, and npm has old versions for a reason. I run 2016 code in production today as an internal application for engineers, and I’ve seamlessly updated webpack and module versions while keeping the same code syntax.
Oddly, despite the immense resources that Facebook has available, Facebook in a browser is slow and glitchy, especially after the most recent redesign.
I can't say I share that experience at all - I make quite an effort to avoid installing their invasive apps, always using the browser - but it's an interesting point
writing react apps in a proper way is hard, very hard. plenty of companies out there big and small can't do it. once you've a professional react dev, you easily recognize the react apps due to the subtle bugs. makes me miss my old team, where our big react app was actually performant.
Can you speak to these bugs? I personally don't agree that React apps impart any standard class of bugs, but maybe that's because Redux is actually the source of all these issues and I've avoided redux like the plague it is
example app: airbnb when you get messages. click a message. and you'll see it will still have an indicator that you've a message, when you've none. airbnb guys are some of the best react guys out there even released open source libraries like enyzme etc. fact is no one can ever get the react thing right. don't get me started on facebook. plenty of bugs in their new app. But for some reason, instagram web is polished.
If a framework/library is very hard to use correctly - even for people who are not only experts, but contributors - then that's a huge red flag against it.
Sure, but if massive amounts of people disagree with that take and think the library is strong, intuitive, and best-in-class, maybe someone is just struggling with understanding the structure of the library
> that it was the first (widely known) framework with those merits
Yep, and my interpretation is that, that (the widely known part) happened because it was backed by Instagram first (if I recall correctly) and then Facebook.
And not to invalidate your point but to add another speculation on why React became more popular...
Yes, Angular was backed by Google, but not used in production in massively successful product as Facebook, or Instagram, and I remember also about writing Directives, Services and it wasn't very inviting for third party packages.
When react came out, I remember the selling point was "it's not a framework, it's a library", "it's the V of MVC" and I think that strategy also helped to let the community create all the other pieces, and long term have a richer ecosystem.
The main reason people started using React is the JSX implementation in React at the time was very innovative. Actually many people working with Rihno in 2006 has talked about using JSX as templating system. FB is just the first one did it. However, now days there are plenty other less bloated front end frame work such as svelte, vue, mithrill that is more easy to use and have JSX support as well. There is really no reason to use React now days.
It took a month to teach angular and a year to become proficient. Knockout or backbone + handlebars were similarly large commitments. React took a day or two to learn and less than a a month to completely master.
I also hear about “react doesn’t have one way of doing thing’s built in”, but it wouldn’t have been as successful if it had. It ate up other frameworks because it was so easy to integrate.
Need more performance in your angular app? Just add an ng-react component. Have an extensive backbone project? You can just gradually swap in react to replace your mustache templates and leave backbone alone. Have a jQuery widget that’s getting hard to maintain? Change to React and the rest of the page renders on the server like it always did.
And of course, you could greenfield with flux, rxJS, or whatever other thing worked for your company. But if they hadn’t been flexible from the start, there wouldn’t have been room to create and test things like reactive programming, redux, mobx , etc
I feel like this "just javascript" trope really needs to die. JSX is not "Just Javascript", and magically reactive `foo = bar` assigments are not "Just Javascript". But frankly, that doesn't really matter one bit anyways; it's fairly nitpicky to object to different control flow syntaxes, when at the end of the day you're just rendering data from a request to screen. I'm sure not many people would be willing to argue that throwing promises (as react suspense supposedly does) is a holy grail of frontend development, even though it is "just javascript".
It's very thin syntactic sugar over a function call. It has a very simple 1:1 mapping to the output code, as opposed to "magically reactive `foo = bar`.
> I'm sure not many people would be willing to argue that throwing promises (as react suspense supposedly does) is a holy grail of frontend development, even though it is "just javascript".
I'll give you that hooks and suspense are straying further from normal js because they don't behave like normal functions do and have special rules for being called; hooks are a DSL for encoding incremental computations into js, and suspense is just the react team fawning over algebraic effects/delimited continuations and trying to reproduce them in js.
But then again, all of these can be defined in normal JS code as opposed to a DSL which compiles to who knows what, so all the rules applying to normal JS and its tooling apply to them.
I should also explain what I mean by "thin syntactic sugar": JSX is a local transformation which doesn't need any external context to be translated to plain JS. `<div className="greeting">Hello</div>` will always compile to `React.createElement('div', { greeting: "hello" }, "Hello")` no matter what; in Svelte, `count += 1` will output different code based on its context because it will compile the whole component as a single unit.
You can easily read the compiled Svelte output and see how each piece of template is wired to the state. There is very little cruft in it, and no real runtime library to speak of.
The debugging experience (not that I have needed it much) is light years ahead compared to going through React's internals.
Yeah, but it's still a DSL and that means that I can't be sure that everything I can normally do in JS is valid there, and external tooling won't be able to make sense of it without additional work.
Those sounds like very abstract concerns - I haven't had any issues on those lines using Svelte for years now. The $: syntax is actually valid JS (labels) so standard JS tooling should always be able to parse it.
> It's very thin syntactic sugar over a function call
Actually, it's not. It's a set of AST nodes. What they compile to is entirely implementation-specific. Look at Inferno.js or Solid.js compiled code for example; it looks nothing like simple function calls. Although obscure, there are also other spin-off ideas floating around like compiling to math expressions or to file system APIs. Even within the scope of React, the idea of allowing configuration for optionally compiling to `h` instead of `React.createElement` for minification purposes has been brought up (and is doable in React proper in user space, and out of box with Preact).
The idea of adding JSX to JS got brought up before and it got shot down precisely because the compilation semantics of JSX are not formally specified.
> all of these can be defined in normal JS code as opposed to a DSL
Right, but my point is that "just javascript" is not a good guiding light if the goal is implementing a pit of success. The crank.js blog post talks about how its author thinks React has gone off the rails pursuing some notion of purity. DSLs are not automatically bad things. I've mentioned Solid.js before and it has a component-based DSL for control flow, and honestly that looks quite reasonable. At some point if one jumps the shark, so to speak, and starts making uncomfortable contortions to stay within the confines of "just javascript" and avoid DSLs at all costs, that amounts to leaving a useful tool unused at the table, where it could have been effective. "When all you have is a hammer", etc.
Ok, but you could make the same point about plain JS as in Svelte. I haven't looked into it too much, but from the readme Solid.js looks more like a compiler than a library, so whatever it outputs when it sees JSX doesn't have much to do with what JSX is usually used for, imo. I've only ever seen it used as syntactic sugar for function calls, be it `React.createElement` or `h` or `jsx`.
> At some point if one jumps the shark, so to speak, and starts making uncomfortable contortions to stay within the confines of "just javascript" and avoid DSLs at all costs, that amounts to leaving a useful tool unused at the table, where it could have been effective.
Personally I find it much more likely that I'll have to bend over backwards when I'm dealing with a DSL rather than just plain code, because the DSL will be really well optimized for one usecase, but has omitted another usecase that I will need at some point, and then I'll have to find some hack to accomodate the DSL rather than just add that one line of code that I could've used if it was just Plain Code.
The downside is that some other things will look kind of awkward and since you're now dealing with a turing-complete language you'll lose analizability, but IMO for UIs those are worth losing if I can just use plain code.
> Personally I find it much more likely that I'll have to bend over backwards when I'm dealing with a DSL rather than just plain code
I think this is cherrypicking at generalities. It's just as easy to run into a wall with "regular code" because some API doesn't provide what you need. Conversely, DSLs often have escape hatches (e.g. https://www.smarty.net/docsv2/en/language.function.php.tpl)
DSLs are useful sometimes. Heck, the entire web is built on top of DSLs: HTML, CSS, SQL, even JSX. I agree that there are valid criticisms to be made about specific implementations of specific DSLs, but I don't agree that those criticisms are generalizable to the concept of DSLs as a tool in a framework designer's arsenal.
I wasn't criticizing DSLs in general, they're great, but in the specific case of web development I prefer React's approach over using a DSL like most other frameworks.
Actually React can stake a better claim to being "just JavaScript" than Svelte. Once you know what JSX compiles to (the rules are fairly basic), React does become "just JavaScript".
That's not the case with Vue, Angular or Svelte. There's framework specific syntax that you need to learn. Eg: '{#each things as thing}' from Svelte's documentation.
It's "just javascript" because if you want to modify anything and have it re-render, it's `let counter = 0; counter++`, not `const [counter, setCounter] = useState(0); setCounter(counter + 1); `. Of course there's template and all, but for regular logic, you don't have to fiddle around abstractions.
I've only dabbled with Svelte, but I agree that it looks like an excellent framework, in itself. Powerful abstractions, succinct and approachable syntax, and it compiles down to a truly compact output. (Not the usual only a few hundred kilobytes when gzipped.)
I ended up not using it though, as the surrounding ecosystem just isn't there. There are two different material design frameworks out there for Svelte, neither of which I could get to work. I ended up opting for Angular+Angular Material, which worked out of the box.
Neither agree or disagree, but just want to say that's exactly how I felt about Angular when React first came out. It wasn't much of a "framework" at all, just an easy way to render onto the DOM from js.
I very much like the idea of compiling code to make direct DOM mutating managable. A very powerful idea regardless of which syntax you end up going with. I wish their website talked more about this instead of having a React comparison on every page.
This comment makes me want to seriously give Svelte a try. I've browsed the docs and it looks really cool, I've always just held back because I feel like I am juggling enough JavaScript libraries in my head already and React has been great.
I did some React development some years ago, and was glad to move away from it because of the ridiculous churn in the ecosystem. It was like building on top of quicksand. At the time, I thought it was because React was new, and new stuff tends to have churn because people are just figuring out the optimum patterns and false starts are to be expected. When I look into React years later, the churn is still going on, which perhaps is a warning sign it just wasn't well thought through to begin with, or that the ecosystem has been captured by architecture astronauts on a lifelong quest for some kind of pure ideal rather than usability and stability.
I agree with your impressions. When I first started working with react professionally one year ago I was thinking I was dumb for not being able to find the logic behind the different moving parts. Now I know that there's none.
biggest issue of Svelte (and vue and others) is that mutable state is marketed as a feature. If you don't like mutable state anyway then it just seems like an inferior way of writing code.
You can't get away from mutable state. Redux, for example, just hides the big `state = newState` mutation but it's still very much there. When a library/framework uses mutable state, it's not that they're saying immutability is an anti-feature. They're just being pragmatic in the sense that mutation has to happen anyways and they consider explicit to be better than implicit. (With that said, some people do argue that immutable JS is in fact an anti-pattern because JS doesn't really lend itself to idiomatic performant immutability in the first place and emulating it as an abstraction adds a lot of unwanted complexity)
IMHO, it's a disservice to yourself to just blindly drink the immutable koolaid without understanding the very real drawbacks it comes with in JS-land. There's a great deal of gray area where explicit mutation is perfectly adequate in ways that overzealous immutability might be counterproductive.
you're arguing based on a very mechanical understanding of why immutable state is important. Immutability is just a solution to the actual goal which is controlled side effect as someone put in the other comment.
Given a side effect (a piece of data is mutated), I need to know what code caused it and when. When you casually throw assign statements everywhere that question becomes really hard to answer.
Not necessarily. Discoverability is a function of colocation, not of immutability. One can write convoluted Redux monstrosities spanning a multitude of files where it's difficult to mentally follow how a dispatched action translates into state deltas (potentially because of too much higher-order abstractions, but also because one action may affect multiple reducers, and that in turn may affect a multitude of React tree nodes because Context or whatever). Svelte reactivity generally only staying within a single file (with some exceptions) is one approach to align system limitations with "pit of success" in a way that happens to use explicit mutation idioms.
> Discoverability is a function of colocation, not of immutability
That's one way to do it, tying discoverability to coding convention (which is usually how collocation is enforced), as opposed to being an inherent property of the code.
could you stop saying redux, in my other comment I said the solution could be anything from react's setState to old-style encapsulation.
The point is, it's kind of odd that someone'd put mutating states in their tutorial proudly as a "feature". It's a thing that you do very conservatively at best.
> it's kind of odd that someone'd put mutating states in their tutorial proudly as a "feature"
Mutating state is actually the de-facto standard across most of the industry. Look at Vue or iOS or Android or Unity or Rails or J2EE... People literally use ORMs to make things more mutable than SQL.
Yes, you can use React setState, but surely it's not lost on you that this is wrapping over a lot of mutable operations too? Worth mentioning as well that setState semantics have some amount of complexity that is attributable to mutability leaking out of the immutability abstraction (e.g. the callback argument, what happens when things like multiple calls to setState occur synchronously, etc). This is the "curse of abstraction" that comes when you are trying to implement semantics that don't exist natively and the abstraction themselves have semantic weaknesses due to the chicken-and-egg problem of the required semantics not existing natively.
again with the mechanical definition of mutability. Ok "controlled" is the word, as I mentioned in other comment state encapsulation (which ORM is) is another solution for this.
These giant monstrosities are really easy to avoid. Redux Actions make locating a state change a breeze. I have worked on giant applications that did not use a redux-like approach to state and it was a horrendous mess.
FWIW, I see a lot of codebases at work (I maintain a large org-wide monorepo), and you'd be surprised at how daunting real world codebases can get. I agree that it's relatively easier to keep a codebase clean when the number of owners is small, but conversely, from my experience, when a codebase has multiple owners, keeping things organized can definitely be a challenge (in some cases I've seen, multiple teams owning overlapping sets of the codebase...)
I've seen codebases with messy redux and codebases with messy non-redux architectures, and conversely also clean codebases with both types of architecture. There really is no silver bullet technology. The "you can write cobol in any language" thing is always going to be a looming plague.
I think it's easy to get into a mess as an applications complexity increases regardless of which state management approach one chooses. Every paradigm has a trade off.
Yeah, I feel for dirty admitting this in this forum, but I still use React & MobX for state management. I get to use simple classes with observable state management. Works just dandy for me.
Redux was shit. It made apps less comprehensible and less performant.
Immutable state isn't really a great idea for stateful applications, and UI development is an almost purely stateful activity. It can easily be said that any UI is fundamentally a state machine...the state of your UI defines the set of actions that are available to it, and the actions taken define the future state of the UI. This is not just true for web UIs, but all UIs. It is just as true for your blender or transmission lever as it is for your react app. And being a stateful problem, you're far more likely to have to change state than you'll have to change how that state is rendered. This puts redux-style solutions squarely on the wrong side of the expression problem. And the fact that your state is essentially a global variable, using it means you spend vast amounts of time trying to thread constantly changing data types through intricately nested pure functions, and it just fucking sucks.
I'm generally a pure functional programming kind of nerd, but when it comes to difficult inherently stateful programming problems like UI dev, game programming, or simulations, nothing is better than plain old encapsulated state via classes (or maybe actors). Anybody who advocates for pure functional approaches to these types of problems has lost the plot.
> It can easily be said that any UI is fundamentally a state machine
This doesn't make sense to me as a criticism of Redux. Redux is a giant state machine. That's all it is. Actions are transitions and the store is the current state. The state transition table is the reducer.
The Redux architecture (and the Elm architecture that inspired it) is about the purest expression of a state machine you're likely to find in any architecture. The fact that a UI is just a giant state machine is the whole reason these architectures were created in the first place.
Redux is tricky and boilerplate-y because of some interactions with React, because it doesn't come with a built-in mechanism for handling interactions with the world "outside" the UI (e.g. interactions with the backend), and because JS doesn't have facilities for immutability out of the box and rather you must bolt on post-hoc solutions.
1. The datastore is a massive global. This already brings to it many of the pitfalls that have caused programmers everywhere to avoid global variables like the plague. But in addition to that, for incredibly complex apps, it is a massive burden to maintain all of your state in one place, without any ability to encapsulate or localize trivial local states. Why do I need a global state tree to determine which option is selected in my select dropdown? Why should I bundle the varying hyperlocalized implications of onChange actions vs onSelect actions with the business implications of things like payment submission states? Why does the state of one button for basic user A have to be bundled in the same data structure as an input element three tabs over, two levels deeper, meant for admin user B with privileged access?
2. Robbed of the concept of benign localized state, all state transitions must be weaved through the entire complex functional tree that renders it. Small refactors of your UI, like moving a widget from one place to a different location in your application "tree", involve not just refactoring the component which renders the widget, but every single component that it passes through, to reroute the data to the right location. It is extremely high touch refactoring, exacerbated by javascript's dynamic nature which is already hard to refactor, and Redux's inability to play nice with static typing tools like Typescript.
Redux's bad ideas do not come from the fact that it models a state machine, it comes from the fact that it models it poorly and forces you into using a giant state machine, intricately threaded and tightly coupled throughout your entire application, when smaller state machines are easier to understand and easier to compose.
Luckily, there is another programming construct that allows you to easily model state machines, and allows you to build them arbitrarily large or small to meet your demands, and allows you to encapsulate data and actions extremely well so that state and associated actions do not leak. They're called classes.
Note that we have _always_ advised that you should avoid putting literally every piece of state into the Redux store, and we do _strongly_ encourage keeping local state in components:
Which goes to the point that redux is not perfect and has its own downsides. I generally take the simplest solution to solving a problem and favor readability over abstraction whenever possible.
A single global state machine and a bunch of small state machines can be losslessly transformed from one to the other. And indeed Redux offers tooling to transform from one to the other depending on preference (this is the Redux Toolkit).
And indeed in that world they don't have to be bundled together. You can have separate state machines for different parts of the page.
> Robbed of the concept of benign localized state, all state transitions must be weaved through the entire complex functional tree that renders it.
As you say, this is really just a specialization of 1 right rather than a separate problem?
> They're called classes.
Classes of course can model state machines since any Turing complete style of programming can model state machines. However, they are significantly different from the classic representation of state machines in that the important part of representing state machines is that you can inspect their state. Classes are meant to be opaque; as you say their encapsulation is a feature.
But their opaqueness prevents the usual state machine composition, which is to link different state machines together based on the states they are currently in. Whether or not that's a good thing depends on the circumstances, but they make it painful when you really do want to represent things as a state machine proper, and not simply as a stateful component. And to the extent that you want a UI to be a state machine, that's a bad thing.
Of course not all parts of your UI should be a state machine, especially things that involve continuous states rather than discrete ones. For truly mind-numbingly benign stateful details (such as which frame a button should be in while performing an animated transition from one color to another) you can just put it directly in the React component and ignore the store altogether, and that's definitely where you really don't want explicit state machines, but rather encapsulated black boxes that are stateless from the perspective of the overall system (which is effectively what a component-based/class-based system looks like when interacting with a state machine).
> I'm sure there are patterns that could help with this.
Indeed there are. Again any Turing complete language can do anything another language can with a sufficient dose of design patterns (in the limit design patterns just recreate another language). So everything I say about "classes" and "state machines" needs to be taken as talking about level of effort not possible vs. impossible, since the former, being Turing complete in most cases, can always model the latter.
But to return to the meat of your questions:
> Why? Is that because you need to know if a transition is valid for a given state?... Not sure what you're refering to by this.
In a manner of speaking yes this is about validity of transitions, but it only shows up when you're making a bigger state machine from smaller ones.
What I meant by "state machine composition" and "important part of representing state machines is that you can inspect their state" relates to the combinatorial explosion of states that occurs when you build a larger state machine from smaller state machines.
When you combine state machines naively you get a new state machine that is the combination of all its child states (the n-tuple of all the child states). These usually (but not always) are not all valid states at the parent state level. That is usually, at the parent state level, only some subset of the combination of every possible child state is a valid overall state.
This means, if you're thinking of things purely as state machines all the way down, you don't want your child state machines to be black boxes. Instead, you want to be able to form relationships between the different child states to be able to constrain them in different ways. In a statically typed language with algebraic data types this might show up as various different sum types that are subsets of tuples. In a language without sum types this often ends up being basically the visitor pattern. In a dynamically typed language this might show up as various assertions about the overall n-tuple. Either way, you need some way to "open up" the child states to inspection by the parent state.
Now sometimes, your parent state truly is the n-tuple of all its child states, i.e. the child state can be any state at all and this is still a valid parent state. At the UI level, this is usually true for anything a user would be unlikely to care about if the page were to refresh and that state was reset, for example the state that governs the animation of a flashing button, or a drop-down menu being open or closed. This is what I meant when I said "black boxes that are stateless from the perspective of the overall system." From the parent system's perspective, if the child component is a black box, you could substitute that child component with a completely stateless placeholder (e.g. a static button) and the overall system would not be in an incorrect state.
And it is precisely these places where it is most convenient to use opaque child states to model things.
However, most "significant" things in a UI are things where we do care about constraining the n-tuple of child states to a subset. We often want certain parts of the UI to gate other parts of a UI (e.g. greying out certain parts of a UI), or want certain actions in one part of the UI to cascade in a specific way across the rest of the UI.
Don't get me wrong, getting back to the beginning of my reply, I'm by no means saying that this is impossible with opaque classes. After all there have been people making perfectly functional UIs with opaque classes for a long long time. It's merely more annoying and bug-prone.
If all your child states are opaque, then all this subsetting of valid states is implicit in control flow rather than explicit in data definitions. And as Fred Brooks put it: "Show me your flowchart [control flow] and conceal your tables [data definitions], and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious."
I'm not sure what you would want from a citation? Somebody other than me saying that the important part of representing state machines is being able to inspect their state? Would you accept prior art? Languages that are explicitly built to model state machines, such as TLA+, generally expose the state explicitly by default and build all their reasoning facilities around manipulating the state directly (TLA+ is indeed generally not an executable language, but there aren't many executable languages I can think of at the moment that really go all-in on making everything a state machine like TLA+ does and so aren't great illustrations of what pure state machines are like; Elm does this as well, but Elm and Redux are the same core approach so it doesn't really count in a discussion about Redux).
> Redux is the purest expression of a state machine possible
Are you sure "pure" is the right word? Last I checked redux very proudly incorporates a certain event-sourcing something idea in it. It's definitely not a "just state machine" library. More like in KFC when you order something, they always make sure that you get their sugar water as well.
What do you mean by "a certain event-sourcing something idea?"
I really do mean "pure." That doesn't mean that Redux doesn't provide other facilities on top, such as React interop, error-handling, logging, etc. But if you don't need any of that and you use the core of Redux, it really is just a state machine representation.
Let's write out the classic finite state machine representation of a locked/unlocked item.
States: Locked, Unlocked
State transition table:
Current State | Transition | Next State
---------------------------------------
Locked | Lock | Locked
Unlocked | Lock | Locked
Locked | Unlock | Unlocked
Unlocked | Unlock | Unlocked
Initial State: Unlocked
That's a textbook definition of an FSM.
This is the equivalent Redux code.
import { createStore } from 'redux';
const initialState = 'Unlocked';
const reducer = (currentState, transition) => {
if (currentState === 'Locked' && transition === 'Lock') {
return 'Locked';
} else if (currentState === 'Locked' && transition === 'Unlock') {
return 'Unlocked';
} else if (currentState === 'Unlocked' && transition === 'Lock') {
return 'Locked';
} else if (currentState === 'Unlocked' && transition === 'Unlock') {
return 'Unlocked';
}
};
const store = createStore(reducer, initialState);
// We're done! Now we can play with our state machine
console.log(store.getState()); // 'Unlocked'
store.dispatch('Lock');
console.log(store.getState()); // 'Locked'
store.dispatch('Lock');
console.log(store.getState()); // 'Locked'
store.dispatch('Unlock');
console.log(store.getState()); // 'Unlocked'
That's it. That's Redux. This is exactly a 1-to-1 translation of the FSM. I don't see how it could be any more direct (at least in JS). Everything else in Redux is optional.
If you think that's the simplest you could get, more power to you. I personally think your example is convoluted for such a simple example, but that's just me.
However, I should point out something that is not actually trivial about your code, by proposing a refactor: your FSM adequately models a car lock button interface, but not the lock itself. If we are modeling the lock, you have two invalid transitions...because it is impossible to lock an already-locked lock, and it is impossible to unlock an already-unlocked lock.
Classes actually shine quite well here.
class On {
constructor(){
console.log('turned on')
}
turnOff(){
console.log('turning off...')
return new Off();
}
}
class Off {
constructor(){
console.log('turned off')
}
turnOn(){
console.log('turning on...')
return new On();
}
}
const toggle = new On();
toggle.turnOff().turnOn().turnOff().turnOff()
// ^ cool, ^ cool, ^ cool. ^ oh shit, typescript really doesn't want to let me do this.
Classes can model your original scenario quite easily (just add a `turnOn()` method to `On` and `turnOff()` to `Off`, but redux can't model my scenario (at least not without more boilerplate and lots of guards). More importantly, the available actions are localized...I don't have to create a monolithic state transition table logic...I only have to concern myself with the possible transitions for any given state. This makes it trivial to add new states or new actions, because I will never have to worry about the combinatorial explosion that can happen in a global transition table.
Let's step back. I think your approach work really well if you model state machine for internal usage.
However, Redux is meant to be for UI work, which is a side effect. You cannot control side-effect. You cannot guarantee that user will not try to turn switch off twice.
Even in idiomatic Redux, the switch case for reducer always include `default:` which mean everything else go here.
As you said, the Redux FSM models a car lock button interface which is exactly what Redux try model.
> I personally think your example is convoluted for such a simple example, but that's just me.
It is indeed all a matter of taste in the end, but I mean it's the exact transcription of a textbook FSM. I don't think your example would get that much simpler with a stable identifier, which brings me to:
> I don't have to create a monolithic state transition table logic
It doesn't have to be monolithic. I just created a monolithic function because it's easier for this small example. It could just as easily dispatch on state (which is what you're doing here), or on transition (which is impossible with your hierarchy), or mix and match them. I can write those examples if you're curious.
Your class example is a good example of a state machine trace (e.g. one that you might often use to create an ephemeral config object), but it's not an example of an actual persistent state machine. As soon as you use a stable identifier the type safety goes out the window.
let stateMachine = new On();
// Wait a bit for the user to do something
stateMachine = statemachine.turnOff();
// Wait again for the user to do something
stateMachine = stateMachine.turnOn();
Depending on how you annotate the initial let, one of those two lines will cause TS to complain (or you have to do a manual cast somewhere which circumvents the type safety).
Indeed if you just care about a state machine trace the same type safety holds in the explicit state transition table case if you just use TS's literal types in the reducer's type annotations. You just explicitly call the reducers you need however many times and then persist the state machine at the very end.
That is your type safety doesn't come from the representation as an object, but rather that there is no stable state machine between invocations, but only an ephemeral trace of one whose intermediate states are immediately destroyed. Traces are valuable! But you could generate the exact same trace with an explicit state transition function and you would get the exact same type safety that way. I can write it out for you if you're curious.
> I will never have to worry about the combinatorial explosion that can happen in a global transition table.
Again you don't have to worry about a combinatorial explosion in a global transition table either if you dispatch on actions or states.
The fundamental difference between what you've outlined here and the explicit state transition table is that in your approach states are first-class and transitions are not (but rather methods attached to states), whereas the explicit state transition function treats both of them as first-class entities.
For places where you really only need an ephemeral state machine trace, rather than a state machine that persists between calls, you don't need first-class transitions because the transitions are all ephemeral and cannot be dynamically called at runtime.
Where you do have users able to dynamically call transitions at runtime, you end up needing to represent those transitions somehow, and you end with something approaching Redux (indeed I think an interesting exercise would be to implement On and Off where the user either presses "a" or "b" at the keyboard to turn on and off with your classes. I suspect you end up with just the Redux approach all over again).
EDIT: I want to emphasize I don't think Redux is free of faults. There are many things I really dislike about it and the React ecosystem it integrates into. But I don't think any of that can be chalked up to a poor representation of a state machine.
As do the reducers. For example the following is a valid type signature.
function onToOff(initialState: "on", transition: "toggle"): "off"
In each of the if clauses those are what the types are inferred as, exactly equivalent in type safety to the classes (and more flexible because you can dispatch on action).
I chose string for simplicity (string literals happen to be distinct types on their own, I could easily use anything else other than strings, e.g. interfaces). Heck it could just be integers and be even simpler.
Look every of your comment is really tiring to read, so fucking long with little substance like redux code. Everyone already knows what redux is, it's really garbage and leaky. For one there is a dispatch function, where is this in the classic "pure" state machine? Why can't state transition be just a function call? Secondly there is no type that you can infer from this, you have to "write typescript" by adding type annotations like an idiot. Or else you could dispatch anything because again there need to be a dispatch function because reason.
The other commenter gave example of implementation as type safe immutable builder. You can even simplify their implementation further by using plain functions instead of classes.
let's back off a bit. I agree with the other commenter's point, which is the really important thing:
> Redux's bad ideas do not come from the fact that it models a state machine, it comes from the fact that it models it poorly
So to me it doesn't really matter whether class is the best tool to implement state machine.
If the argument is "you can do it with class/function/language feature x, however it's a lot of work to achieve composition or whatever", then the same can be said about redux:
it kind of looks like a state machine, but in order to have composability/validation/static types etc, you still have to apply a bunch of "patterns". So it's just as effective (or ineffective) as the approach of rolling your own state machine.
But that's precisely where I strongly disagree. Redux's problems do not come from a poor implementation of a state machine, but rather the interactions between state machines and stateful, non-state machine representations.
Again, Redux is the purest expression of a state machine possible in a general-purpose programming language (as opposed to a specialized one like TLA+). It maps one-to-one with the usual textbook definition (technically textbooks don't have a rigorous definition for state machines, but rather finite state machines, but usually by "state machine" we just mean you take a normal FSM and just relax the finite bit by introducing some infinite component into the overall state such as an arbitrarily long list). The store is the state, the actions are transitions, and the reducer is the state transition table.
Redux's infelicities come precisely from the fact that it needs to interact with things that are not modeled as state machines! Namely, interacting with black box children and the outside world (mostly sending stuff to the outside world, Redux comes with stuff out of the box to deal with just one-way inputs from the world). And Redux doesn't come with ways out of the box to deal with those so you need to layer more stuff to make it all play nicely together. But you need at some point to interact with them to work with the JS ecosystem so you need that stuff.
If you had state machines all the way down you don't need any particular patterns other than the fundamental state machine description! Especially with Typescript's structural types, everything just works (you just pull out chunks of your store into little stores and pull chunks of your big reducer out into little reducers, it's literally just cut-and-pasting chunks of your code and giving it a new name and making new state machines all the way down).
The friction comes from interacting with React, dealing with the outside world, and figuring out various decisions of what to do about data structures that the JS stdlib doesn't decide for you out of the box (e.g. what immutable library should one use). Even the choice of mutable vs immutable is something that is decoupled from Redux proper (it turns out for incidental reasons it's easier for type systems to properly scope local effects with immutable data structures than mutable ones, but that's more an artifact of Typescript than Redux, see e.g. Rust's type system for a counterexample).
If all of that was just more state machines (and the stdlib issues were taken care of) then Redux would be absolutely lovely.
And that's not just a pie-in-the-sky hypothetical. E.g. Elm is basically what Redux would be if everything was state machines and works great in all the places Redux doesn't (Elm's infelicities in turn are of a different variety that stem from some limitations of its type system to properly express some of the structural types you'd like state machines to have, in particular a lack of structural union types that often requires duplicating the state transition table in certain ways as you break a larger state machine down into smaller state machines).
I think you’re illustrating one of redux’s real downfalls: it can be used to hold the entire state for your UI, even though it shouldn’t. All your complaints are solved when adhering to the official recommendations. Maybe you got into redux early or worked on a codebase that took things too far.
A global state object is one of the nicer features when dealing with redux.
> you're far more likely to have to change state than you'll have to change how that state is rendered. This puts redux-style solutions squarely on the wrong side of the expression problem
Maybe, it's a bit hard for me to describe succinctly.
Your UI as an application likely has dozens, if not hundreds or thousands of potential states. And the more you develop the feature set of your UI, the more states you will introduce to it. The data that represents these various states is going to constantly evolve to be able to represent them: you'll have evolving business state, evolving widget states, evolving URL states, etc.
The expression problem can be summarized as a practical comparison of two types of extensibility: writing functions, which extend through composition, and writing classes, which extend through inheritance. The choice of which one is better starts with asking what is more likely to change: your data or your computation over that data.
If you have a problem where your data types are relatively static, but the computations you do on that data are constantly changing, pure functional programming is where it is at. You just write different functions to do different things, and then compose them together to do what you want. This sort of processing is perfect for a ton of usecases...things like data pipelines and etl, machine learning, web services, etc.
If you have a problem where your data types are constantly changing but your computation is relatively static, you're going to have a very bad time with a pure functional approach. You'll end up having to edit multiple functions just to change a single data type. When you decide that you no longer want to represent your state with an Array<number> and instead want to represent it with a Set<CustomObject>, you're gonna have to refactor every single function that the original version passed through. And with a massive global state object like Redux, that means not just refactoring the component that renders it, but potentially every single component above it in the HTML tree, modifying them so that they can pass it through to its final destination. That is the sort of burden that happens when you fall on the wrong side of the expression problem.
This was true before the age of Trump and the ongoing internal war, highlighted by Bari Weiss and more, between the old guard and the new ideologues. I am most definitely not alone when I say the weight of their words have diminished greatly.
Neither did Vue, or Django, or Rails or dozens of other successful and widely adopted technologies. Svelte's issue is probably that it came out a little too late: companies have adopted React, Vue or Angular, and have sufficient inertia (existing codebases and hard-won experience) that there is little incentive to switch just for a slightly nicer syntax or state management model.
Sounds like you're describing Vue. Is their any substantial advantage over Vue that you're aware of? I think a lot of people, including myself, are just uninterested in arbitrarily and subtley different solutions to the same problem. I've used react, and it's just not as compelling as anyone made it out to be. Neither is Vue compared to React for that matter, but it appeals to me a little more.
So I'm a Vue developer who hasn't ever actually built anything in Svelte, but Svelte uses a really cool model that's completely different from Vue and React. Where Vue/React use a virtual DOM, Svelte is compiled to essentially vanilla JS. Instead of having render functions that fully replace a component, Svelte updates just the parts of the DOM that need to be updated. Because of the dramatically reduced overhead, pretty much any UI should run more efficiently under Svelte.
I'm still a big fan of the developer experience Vue provides and I'm locked into a framework for my work projects so I won't be switching those over any time soon, but I've been meaning to try out Svelte for a personal project some time.
Stencil has is also a component compiler and has a very clean API (declarative through annotations like in modern angular) and standard TSX.
However the issue with both svelte and stencil is their lack of ecosystem, even if they have interop with webcomponents, today the market is too small.
Yes, both of the creators now work on the React team (Dan Abramov and Andrew Clark), but it's always been an independent OSS project, and is currently maintained by myself and Tim Dorr.
As far as I know, FB barely even uses Redux at all internally, and that only in isolated particular teams that chose it themselves. My understanding is that they mostly use Relay for the new FB.com, and still have a lot of old-school Flux in there as well.
My commentary is more directed at React and Jest (as I try to steer away from that ecosystem, even though it's ideas aren't all bad):
- As GP indicates, React has had multiple API implementations to solve similar problems, feature bloat, leading to opinionated and differing best practices, mixed in with Junior devs can be a nightmare
- With Jest, I find the entire thing as a very aggressive wrapper and very tied to React itself, features like unit test `snapshots` are developer slack-off bait and have the benefit of further entrenching itself into the repository, arguably an e2e test would validate the same thing
>Yes, both of the creators now work on the React team (Dan Abramov and Andrew Clark), but it's always been an independent OSS project, and is currently maintained by myself and Tim Dorr.
The source I have says that Dan Abramov created Redux while working at Facebook on React[0]. So while Redux has never been under the official governance of FB, it was created by FB employees for a FB project (React) while in parallel working on that project.
That's wrong. Dan created Redux while he was actually unemployed. I'd have to check the exact timeline, but I believe he left Stampsy in late 2014 or early 2015, and was actually getting some funding from an OSS collective-type thing while he developed Redux in preparation for React Europe.
Redux was developed in June/July 2015, and demoed at React Europe on July 5, 2015 [0].
Redux 1.0 was released on Aug 14, 2015 [1].
Per the tweet linked from that page [2], Dan didn't join Facebook until November 2015, well after Redux had been released.
So, he most definitely was _not_ working at FB when he developed Redux.
Doesn't make sense, there is nothing that benefits facebook by "dominating" a tech niche. They dont profit off of development platforms (android and .NET) like google and microsoft
For me Svelte is between React and Elm. It has some of its own syntax and operator for some benefits. It is better than React in some sense (obviously not in maturity) yet it is worse than Elm (no ML features). If I need to learn some new syntax it is better worth it (no run time exceptions). I am not sure what is the long term plan for Svelte. Is there any plan to address the shortcomings of JS (for example: undefined is a not a function)?
Have you ever worked on very large React applications without Redux or Saga? I just cannot imagine any other way of keeping my application sort of understandable.
> I find this in contrast to React which is often baffling and incoherent. I say this as someone that has used React professionally for 6 years. They've changed their mind at least 3 times on the API and none of it fits together today. Hooks and classes don't mix, lifecycle methods are still out there and just as confusing today as they were years ago. Mixing state and functions. It's just a horrible bag of half-baked bad ideas. And it's not even batteries included.
This, so much. It has been so frustrating to have to go from es5 classes to es6 classes + stateless functions to stateful hook oriented functions that do nothing but create just a new type of class, but also with es6 classes but with different methods. Seriously, WTF?!
I really like the ideas behind Svelte, but some things just really do not mix well with it. Like leaflet or d3. For some reason, mixing declarative rendering approaches with non-declarative approaches results in completely mind boggling buggy interactions that aren't easily understood. And typescript integration feels tacked on and doesn't play nice with some of the semantic differences between svelte and javascript, which makes me doubt how much you can really say that it is just Javascript. I mean the entire concept of a variable has completely changed to become reactive, and at that point it's not really javascript anymore, it's something that compiles to javascript while looking almost exactly like javascript. I'd love to give it a deeper look, and I probably will soon, but I'm quite skeptical of what might be hiding underneath the covers.
I'd agree on the architecture astronauts, but it would still be useful to see some good open source code bases using Svelte beyond the "todo list" level. For example:
- has a test suite
- integration with some backend framework (including secure auth)
I found animations a bit of a struggle, especially when combining multiple of them. There were performance bugs with some of the combined animations, and the Svelte documentation didn’t make it clear how animations could affect things like the focus on a text box.
> writing complex React components feels more like admin; a constant worry that I'll miss a dependency in my useEffect call and end up crashing my browser session.
I don't understand this worry, but I guess that happens when you try to do everything with hooks, the way I settled in my approach is to use React components just for the view without hooks, nor lifecycle logic, just dumb components with ocasional local state (useState)
Then using Controller pattern, within the controller I use 4 types of hooks, 1 navigation (useHistory), 2 read global state (useSelector), 3 dispatch an action (useDispatch), 4 useEffect() to execute on props change or component mount.
That's it, that's where the realm of React and UI ends for me, everything else is in Ducks, Ducks are single file or folder per module that contains Actions, Epics (Obervables) or Sagas (generators), Reducers, initial state of store and Typescript types.
I really like this separation of concerns:
- dumb components: to worry just about the looks and local state.
- controllers: to worry about data in, actions out, life cycle and navigation.
- Ducks: Management of data fetching and global state.
I wrote this from the top of my head, but if anyone is interested I'm thinking about writing a demo repository and document this approach, maybe others have done it already this is not new, but practices that I've been collecting along the way.
This sounds really interesting, don't get me wrong, but it also shows just how insanely novel and complicated modern frontend dev is.
Let's break down the terminology:
- hooks
- useEffect
- useHistory
- useSelector
- dispatch
- action
- useDispatch
- ducks
- Actions
- Epics (Observables)
- Sagas (generators)
- Reducers
Most of those terminologies don't come from a general programming paradigm, but are the domain language of specific libraries. And I've seen more, with some places establishing patterns like Compound/Molecule/Atom and such. There's no consistent, underlying principle with them, though. If you're invested in Sagas then you're stuck with a bizarre threading model implemented with generator functions and it's pretty much all-or-nothing. If you're invested in Redux then there's no breaking out of that either, you're always going to be contending with shared global state.
Not to mention terms that do come from a general programming paradigm, but have a very narrow practical / framework-specific use within frontend dev that makes it even harder for beginners (e.g. "thunk" might be a general term, but I bet most people googling it are just trying to get some Redux tutorial to work, and will end up going down dozens of rabbit holes trying to understand the general concept)
I really hate to think how many collective hours have been wasted on new coders who learn JavaScript as their first language --- especially when starting with React and Redux, as it's often taught --- and end up spending most of their time spinning their wheels on these domain-specific complexities, when they haven't even gotten to "while loops" yet. This may even be harder for beginners than understanding, say, one of the classic CS sorting algorithms. New coders don't realize though that whatever degree this stuff is harder, it's a sign of bad tooling or documentation, not that it's somehow intrinsically a harder problem in math / CS, since if you're new you don't know what to expect
React's hooks (and some JSX style things in general) are quite pernicious there because they essentially break JS: you can't use them inside an `if` or a `for` or a `while`, or even within another function. You're no longer writing Javascript, you're writing a restricted subset to avoid undefined behaviour.
That alone is a concern, because any junior programmer getting an introduction through React is going to get a twisted understanding of the language itself.
In some ways JS is an excellent first language- beyond its broad applicability, it has an extremely "average of all the others" set of features and primary syntaxes. An experienced JS programmer will be conceptually comfortable with Python and Ruby, syntactically comfortable with Java and C, etc.
But in terms of the industry-standard practices/ecosystem, it is a terrible first language if you want to gain a generalized understanding. What you describe here is extremely true; I've seen it in practice:
> any junior programmer getting an introduction through React is going to get a twisted understanding of the language itself
I've talked with otherwise very capable JS devs who have built impressive things with multiple frameworks, but don't have a good handle on fundamental concepts like the difference between the two kinds of function syntax (they may have only ever used one), how Promises behave outside of a narrow subset of situations, what NodeJS actually is (despite having used it), or even the difference between passing by reference vs value.
Many of these frameworks create such a narrow lens into the language itself, add what are effectively (sometimes literally) their own DSLs on top, sprinkle in some magical behavior, and when all is said and done you hardly end up using JS at all.
Obviously there's a benefit from doing things this way or it wouldn't have happened. But I can't help but think the technologies that work well for industry are having a detrimental effect on a generation of new programmers.
I definitely think the tendencies you’re describing are maximized in the JS ecosystem, but it isn’t clear to me if it’s something about the specific language or culture/tooling... or if it’s just where so much of the industry grunt work is getting done.
And so much of the industry everywhere is about “do X to get Y result” and any further reflection is kindof a luxury. How does your package manager work? Part of the point is not to know, which works until it doesn’t. So we all get trained into basically a culture of issuing commands in a manner not too different from the machines we ask to do the work, and developing a model of what’s going on is either bonus study or earned by running your hands over the sharp edges when you’re bucked onto the unhappy path.
Which definitely happens outside of JS. I suspect it happens more inside because JS functions as maybe the biggest funnel into the field.
> This is not correct. Arrow functions have the `this` value of the outer context.
You are correct, I wasn't clear. I was describing the end effect of free floating arrow functions VS an arrow function declared within a class.
When free floating, trying to access 'this' can (will) result in unexpected behavior, when inside a class arrow functions operate exactly as people expect a class member function to operate in other languages. (In contrast to having to otherwise bind JS functions to 'this')
In both cases the arrow function is acting the same way, but most intro to JS lessons don't explain it.
Indeed the way it was originally explained to me is that "arrow functions in classes auto bind to this" which is hilariously inaccurate, but the end effect is as if that was happening.
> even the difference between passing by reference vs value
You can only pass by value in JS so maybe that's why. It is a bit confusing because of how objects work that it feels like you are passing by reference. You are passing reference as the value.
When it comes to primitives you can only pass by value, but with objects and arrays you can only pass by reference. And this applies to comparisons too: when comparing objects or arrays you are comparing by reference, not deeply by their inner values. If you want to compare them deeply you have to take some additional steps to make that possible, and all of the different strategies for doing so have different trade-offs.
I once talked to someone with >5 years front-end experience, including some team-lead experience, who thought the difference between == and === was that === compares objects deeply. That is very, dangerously wrong. The only way I can imagine he got by for so long with that misconception is that he'd always used ImmutableJS.
Everything in JavaScript is passed the same way, whether primitive values or objects. Trying to graft a distinction between pass-by-value and pass-by-reference on top of this single passing method is unlikely to do anything other than confuse people learning JavaScript.
You're talking about the call site; I'm talking about the semantics. There is undeniably a crucial semantic distinction, whatever terminology you want to use to talk about it.
You don't pass by reference in JS. You pass references by value.
Passing by reference has a well defined meaning in computer language terminology. It means passing something that references the variable outside the function. Something you can't do in JavaScript
void someFunc(ref int v) {
v = 456;
}
var foo = 123;
someFunc(foo);
write(foo); // output 456
Above we are not passing 123 (the value of foo) into someFumc, we are passing a reference to foo. This is what pass by reference means.
same if foo was reference to an object
void someFunc(ref Object v) {
v = someOtherObject;
}
var foo = someObject;
someFunc(foo);
// foo now refers to someOtherObject
In this impossible in JavaScript. It does not have the concept of pass by reference. It only has pass by value
function someFunc(v) {
v = someOtherObject;
}
var foo = someObject;
someFunc(foo);
// foo still refers to someObject
Above the value of foo, which is a reference to someObject, that value is assigned to v in someFunc. v's value is set to reference someOtherObject. foo's value is still someObject as there is no way to "pass by reference" in JavaScript
I would argue that Javascript is a hybrid of pass by reference because of the case below though.
function someFunc(v) {
v.myvar = 456;
v = someOtherObject;
}
var foo = someObject;
foo.myvar = 123;
someFunc(foo);
// foo still refers to someObject
// foo.myvar is 456
Banning our team from passing objects around except in specific, discussed, cases got rid of lots of difficult to track down bugs.
You'd be wrong. Again "Pass by reference" is "well defined" in computer languages.
Passing a reference to an object is not "pass by reference". Your example works the same in Java and C# and probably Swift and many other languages. But you aren't "passing by reference". "pass by reference" specifically means passing a reference to the variable, not the variable's value. Javascript always passes the value of the variable, it has no ability to pass a variable by reference.
Great explanation of something that has caught me out multiple times (I occasionally dabble in JS).
Having spent my career in languages with pass by reference it feels unnatural not to have it. I suspect if I started with JS this wouldn't be such a problem.
I'm not sure I agree with your framing of the precise terminology ("passing a reference to an object, by value" vs "passing an object by reference"), but it also isn't important. What's important is this:
let a = { foo: "bar" }
let b = { foo: "bar" }
console.log(a === b) // false
and this:
let a = { foo: "bar" }
let b = a
b.foo = "blah"
console.log(a.foo) // "blah"
This is the kind of misunderstanding that causes insidious bugs.
> I'm not sure I agree with your framing of the precise terminology ("passing a reference to an object by value" vs "passing an object by reference")
The distinction is that, if JS truly was pass-by-reference for objects, you could change what the reference was pointing to. But you can't.
I agree with you that objects/references are a stumbling block for new programmers in JS. You have to understand how these things work but there's no explicit concept of a reference, which means it can be tricky to learn.
> you could change what the reference was pointing to
That's only true if you're passing the pointer by reference. That's different from passing the object itself by reference.
I did just look up the C++ semantics and learned something: technically reference-arguments don't have to be implemented by the compiler as pointers, though in practice they mostly are. So in that sense it's technically more correct to say that JS works with object pointers and not references. But we're really getting quite deep into the weeds at that point.
This is the kind of misunderstanding that causes insidious bugs.
That's how all mainstream languages with a GC work, Java, C#, Ruby and Python, it's not particular to JS. And this why why languages like Clojure are nice to work with.
but it also isn't important
It's important if you really want to know why your examples work they way they do.
We're specifically talking about new programmers who haven't used any of those languages, and who are being reared in the JavaScript ecosystem where certain patterns reign that obscure this language behavior. That's what the original discussion was about.
> It's important if you really want to know why your examples work they way they do.
It's really not. Pass-by-reference and pass-a-pointer-by-value are subtly different concepts even in the C++ world, and using terminology that involves the word "pointer" opens up a whole other can of stuff that needs explaining.
Very true, while I appreciated React Hooks for making React easier for beginners by avoiding the giant can of worms that is OOP mechanics in JavaScript, they are also very magical and even more framework-specific.
That said, vanilla JS can be hard like this as well, especially as "solved" legacy issues still regularly crop up in tutorials and code-bases. Many times I remember helping newer folks with challenges they found online that seemed to them like they were testing hard computer science problems, but were more just trivia to navigate some bad design decisions of JS (eg gotcha problems on "this" / binding, IFFE, hoisting, async/promises/callbacks, to name a few)
I decided that react was going down since the introduction of hooks. Before that I was using higher order component (hoc) perfectly fine and there was even libraries like recompose/recompact that made HOCs more convenient. They're really just functions (no magic, not like quote unquote) and they don't invite devs to convert everything into them and break portability/separation of concerns like hooks do. They're also highly composable (there are no such thing as rules of HOCs), you can pass HOCs as params or put them in conditional code branch or whatever you feel like.
The mistake those beginner make is listening to some evagelists that will tell them they need everything, from React, TypeScript to Redux, routers, thunks/sagas etc.
As a beginner, you should start with plain JS/TS. When you dealt with the hell of manipulating DOM elements all over the place and untraceable UI updates after hours of debugging, you should move on to some UI library. Only then will you understand and see what problem it solves. Then you should go with React, but not hooks or redux. Use plain old classes. When you felt the pain of distributed mutable state all over place, you will be open to hooks or redux etc. Still, do not add a library for thunks. Only after you realize how much boiler plate code you write to deal with async stuff, you will see a value in the added abstraction.
A beginner should start simple, and really feel the pain. Only then will there be an "a-ha!" moment where you can judge if a library/framework actually helps you and provides value.
The biggest issue in FE world is people proclaiming some given conglomerate of a library/framework soup as the silver bullet to all problems.
Meh, beginners are going to spin their wheels when they get to the point where they're trying to build anything, especially using a web/mobile/desktop client framework, because that's 99% of what software development is.
Don't feel to bad for them, that's our entire craft. `while(true)` and sorting algorithms are not.
To clarify, I guess what I'm saying is it's often mistaken that this complexity is somehow intrinsic or unavoidable to the craft, when in fact it's completely avoidable.
Bad (but popular) tooling isn't new --- I remember learning PHP4 was like an encyclopedia of gotchas and defensive programming --- but I feel that even a disaster like PHP4 was a lot more approachable for new coders than the state of the art now, and that the skills learned overcoming these things were more portable outside of the framework / language. All of this definitely makes my job more secure, but, I dunno, still feels like a real shame.
This is nothing Frontend or JS specific. I've been around in BE (c#) long enough, and the "new shit" adds also terminologies that have nothing todo with C#/.NET itself and were basically "unknown" to any C# developer (or Java for that sake) 10 years ago:
- Dependency Injection/IoC - Containers, Resolvers, Scopes
- Microservices (Circuit Breaker anyone?)
- DDD + CQRS (i could fill a whole page with terms coined by those)
- Mediator Pattern
etc.
If you buy into a certain techstack, it will always bring associated patterns that have nothing to do with the original language/idiom.
Once you understand hooks in general, any specific hook should be quite straightforward so they can collapse into one. Hooks are quite novel, but I don't find them to be overly complicated unless you're dealing with an already complex situation.
Dispatch, actions and reducers can be collapsed into one since they rely on each other. I think this paradigm is not too dissimilar to message passing with something like RabbitMQ. Reducers are a common concept.
Ducks and Actions, Epics and Sagas are just a convention. It's not required to use React or Redux.
If you try to learn everything at once, of course you're going to get overwhelmed. But the same is true for almost any area of Software Engineering.
I think it’s totally fair. I discourage redux and all related patterns for this very reason. Well that, and I think local state is actually a good thing (and moving state out into separate files a bad thing).
To me the proxy object (or proxy class) is really the best abstraction - much like valtio. Or a compiler like svelte.
There’s no reason for ducks, epics, sagas, etc etc. We have a programming language already, we should use it.
It sounds like you're agreeing with me. You don't need everything up front, so listing everything is a bit disingenuous.
It's the same explaining a fully kitted out backend (Routing, caching, authentication, etc) and then going "see, it's so complicated!". Well duh, it's a big and complex system.
I think the issue with valtio is that there's no central store. As far as I can see, you'll end up having these proxies scattered around your codebase or you'll end up re-creating Redux and centralizing them.
So what. I've been a backend dev before I turned to front-end and web stuff. React has like a dozen top level functions and approaches. No backend project I have ever seen was this simple to program. How much more simple does it have to get before people stop complaining?
Novel - not, in my opinion. How to do GUI is known from 1970-ties. Today's single page apolication frameworks slowly re-discover the wheel. It is funny to observe.
The very important difference is that we do distributed systems now (admitted, X originally was also distributed, but only the drawing/events parts, not on the data level). A SPA is a distributed system, with all its drawbacks & pitfalls.
If you ever did VB development in the 90s, you will know how easy it was to get a basic app running with those fancy Drag'n'Drop RAD tools. But there was no networking, everything worked offline. But then came the web and the networks, and now everything has to be a distributed system.
There absolutely was a remote part to most VB6 apps. Usually they fetched data from some MS flavour of a RDBMS. Of course they were desktop apps and had to be installed individually but this “webify everything” mania seems to have smothered all dev productivity and experience on its altar.
Yes, and I remember precisely how many apps just encoded the DB credentials inside of the code - with no access/rights abstraction whatsoever. So basically godmode. Thats why we use service layers in Apps today (REST, SOAP etc.) instead of granting a user's process godmode access to the DB.
Why godmode? If your DB user is restricted to specific tables and columns, he is controlled.
If you eventually get access to the data, does not matter how many layers are in-between (REST, SOAP). You still got the data, so that's a "god mode" in the same sense.
Row/Table level access controls don't work; you can't model use cases like "a user may not insert an order if he has 3 ore more unpaid invoices" or "a user can not insert anything until his password was changed in the last 30 days" etc. We have been there, it doesnt work. Thats why we use service layers of the DB to assert business logic.
> Most of those terminologies don't come from a general programming paradigm, but are the domain language of specific libraries.
Not really? Most of these concepts can be linked back directly to earlier from CS theory and/or other languages. For example, Redux taking inspiration from FRP and functional lenses, or (most pertinent for this thread) hooks being a facsimile of algebraic effects in JS. As much as I like these concepts in general, I feel some go too far against the grain of what is idiomatic in the language and incur unnecessary friction/spooky action at a distance because of it. For example, here [1] are some well thought-out critiques of hooks and why people struggle with them from experienced JS framework authors.
You are talking about React here, not Frontend in general. Angular has different concepts. Steep at the beginning, but in my point of view easier in the long term with a stable API.
I agree. Frontend is tough, but so is development. ;)
Yeah this is the problem with the React ecosystem. I'm intimately familiar with this stack and used to use redux with "duck" modules professionally, complete with sagas and epics, and rxjs and the whole kitten, so it's not that I don't understand all the terms and am just overwhelmed. It's just that once you stop using Redux, it becomes so much easier to build and manage your app, and you don't even realize how much you're complicating everything by using sagas and actions creators and reducers and selectors and.. and.. and.
You don't even realize how bad the boilerplate is until it's all gone. With svelte and some writable and readable stores, I can build apps in a fraction of the time because every time I want to make a change I don't have to start with an action creator, than integrate that into the reducer, then write a selector, then pull it into my component. Oh I guess I need to write an epic with rxjs to fetch the data.
Svelte has 2 primitives that replace all of this: readable and writable. You can create a writable store and you call `.update` or `.set` like react setState. Want to separate your update logic from your component logic like in redux? Easy, just export functions to update the store instead of calling `.update` in your components.
Then, there's readable. Now, readable can be used like a reselect selector, but it's also far more powerful. You can subscribe to changes from a writable, or even other readable stores. You can create a readable that fetches your data. You can create a readable that subscribes to firestore collection or document. You can make a readable that combines data from multiple sources. It's all clear and composable. And I didn't have to write an action creator with an action constant called `LOADING_PRODUCTS`, and one called `LOADED_PRODUCTS`, and then one called `UPDATED_PRODUCTS`, and for good measure we should also create an action "PRODUCTS_REQUEST_FAILED" that we'll emit from the epic.
> Svelte has 2 primitives that replace all of this: readable and writable. You can create a writable store and you call `.update` or `.set` like react setState. Want to separate your update logic from your component logic like in redux? Easy, just export functions to update the store instead of calling `.update` in your components.
This sounds very similar to the experience of using MobX. I evangelize it as an alternative to Redux every chance I get; it's incredible how practical and pleasant it is to work with.
I actually came to this thread expecting more people to call out MobX. When I use MobX with react, I find it to be as simple and _fun_ as the author finds svelte. Most of the problems people tend to raise with React are problems that are solved by observable state objects.
I also evangelize it whenever I can -- I don't want MobX to be forever doomed to its status as a cult hit!
It's baffling to me how distant of a second it is in terms of popularity. Instead of forcing you to contort your program into a special paradigm in order to deal with reactivity in a sane way, it simply makes reactivity a non-concern (while remaining pretty simple and predictable when you do actually care to dig beneath the magic). You can write code in a way that's natural and simply not think about reactivity most of the time, and you'll even get better performance in many cases because of the highly-granular pub/sub that it sets up automatically. I cannot praise this paradigm enough.
And then there's Vue.js which essentially has MobX functionality built in. When you want to update the state, you just...update the state, which can be a plain JS object that knows nothing about the front-end framework. I've used it for several medium-size projects and haven't gotten close to needing anything like Redux or Vuex.
Yeah, although when I played with Vue a couple years ago it seemed like it wasn't quite as powerful as MobX (particularly when you start stacking up a graph of computed values, or when you want to create side-effects of your own). But definitely a similar idea.
It is also fucking magic. I love it when it works, but I have no idea why it doesn’t when it breaks. The same is not true for Redux, which is so simple I can build a house with it.
The "magic" is very simple and understandable if you read the docs; I fairly quickly gained an understanding of it that allowed me to dig into the odd performance issue or bug without much trouble
1) Observables are properties on objects; when their getter is called, it becomes "tracked" (subscribed to) by the currently-running tracked function. When their setter is called, it publishes to all subscribers. This happens through JS proxies for plain objects, and class decorators for class instances.
2) A tracked function is anything that must be re-evaluated when an observable it depends on is modified. This includes React render functions, computed functions, and any other side-effects you've created via autorun(), reaction(), etc. Though the idea is to avoid side-effects, so 90% of the time it's just render functions and computed functions. In these cases it amounts to a cache-clear on a pure function: "the return value will be different, so re-evaluate it and use the new return value". A computed function is an observable property getter that's also a tracked function (it doesn't have a corresponding setter; instead, it publishes when it's published to).
3) Tracking works by a) marking global state before a tracked function is run, b) making note of any observables that report back during that time, c) clearing the global state (and subscribing to the observables) when the tracked function completes. Because this is done temporally based on before/after the function has executed, any nested function calls get treated exactly the same as the top-level function call. There's no special magic necessary for that.
That's about all there is to it. Everything else in the library is just another permutation on these concepts.
Great explanation! And keep fighting the good fight pushing MobX ;) Pretty much everyone I know who has tried it has ended up loving it, but it can be hard to convince people to give it a go (because of FUD around "mutability" or "magic" or whatever...).
We specifically recommend using our official Redux Toolkit package, rather than `typesafe-actions`. RTK is already written in TS and designed for a solid TS usage experience:
Redux Toolkit is great and removes most of the usual Redux boilerplate which seems to be the most often used argument against Redux. Additionally, I usually use a function which uses createEntityAdapter, createSlice and createAsyncThunk methods to create Ducks bundles for each REST API resource automatically. As a result I get all async action creators, reducers and basic selectors for some REST API resource with a couple of lines of code.
If you like the stuff in RTK so far, I think you're going to like our upcoming "RTK Query" API, which adds a React Query-inspired data fetching abstraction:
>You don't even realize how bad the boilerplate is until it's all gone
I 100% agree with this, I also started out working with Redux in the fairly early days of React and while I do feel that I did learn a lot from it and have used Redux-inspired patterns for certain parts of applications since then, the productivity gains I got from switching to MobX for state management can't be overstated.
Sure, you could argue it's less "clean" or whatever, but honestly 99% of the time it just works, and I'm happy to have the 1% of time where you're debugging where you forgot to add "observable" or whatever in exchange for the 99% productivity boost.
Svelte does look really interesting and follows a similar kind of model to how I work with React and MobX, and does away with some boilerplate/gotchas.
My main concern using it would be around maturity of libraries... while it does seem pretty batteries-included, I have got used to being able to just pull in a library for x, y or z in React and I'd be worried if I started a big Svelte project, I might find myself kicking myself down the line e.g. if I need some component (data table or whatever) that has a mature option available in React. Perhaps people who've worked with Svelte professionally can comment on whether this is a valid concern?
I do get the arguments that you're making here about Svelte, and don't disagree. That said, "Modern Redux" code is very different than what you've probably seen even just a couple years ago. We've introduced newer APIs like Redux Toolkit, which is a set of utilities that provide a light abstraction to simplify the most common Redux tasks, and the React-Redux hooks API, which is generally easier to use than the traditional connect API.
If you get a chance, I strongly recommend reading through the newly rewritten official tutorials in the Redux docs, which have been specifically designed to show both how Redux works and show our recommended practices:
- "Redux Essentials" tutorial [0]: teaches "how to use Redux, the right way", by building a real-world app using Redux Toolkit
- "Redux Fundamentals" tutorial [1]: teaches "how Redux works, from the bottom up", by showing how to write Redux code by hand and why standard usage patterns exist, and how Redux Toolkit simplifies those patterns
The older patterns shown in almost all other tutorials on the internet are still valid, but not how we recommend writing Redux code today.
You should also read through the Redux "Style Guide" docs page [2], which explains our recommended patterns and best practices. Following those will result in better and more maintainable Redux apps.
Finally, we've got an upcoming "RTK Query" API that we're finalizing and adding to Redux Toolkit (inspired by tools like React Query and Apollo) , which should drastically simplify the data fetching story for Redux apps.
Oh, look, slices, and thunks, and asyncThunks, and extraReducers, and adapters, and fifteen files to tie all this together for every small piece of api or data that you want to send around.
Most of it just comes from the fact that Javascript doesn't have a good built-in way of doing these cycles fo data/store updates. And still, I don't know where the sweet spot of Redux it: it's an overkill for small apps, it's unmanageable in big apps.
However, I will concede that the new hooks like `useDispatch` are nice
I'm not sure how you're looking at that docs page and saying that it "devolves into the same old". That page explicitly shows how RTK simplifies existing Redux patterns. Yes, concepts like "thunks" and "reducers" still exist. Yes, there are new APIs like `createSlice` and `createEntityAdapter`, which have their own options. That's because we've seen how people are using Redux, and have built those APIs to help solve the use cases people are dealing with. But no, the code you write today is drastically different, and we've gotten tons of positive feedback about how much people enjoy using RTK.
It's also definitely _not_ "15 files to tie this all together". In fact, we specifically recommend writing all the Redux logic for a given feature in a single "slice file":
> That page explicitly shows how RTK simplifies existing Redux patterns.
The page is a tutorial for a TODO list with only the simplest and the happiest paths. It's not a forum just because you called it a forum, by the way.
OT: I wish the JS world would stop doing TODO lists. A TODO list is a glorified `Array.push`. If it takes your library/framework 10 000 words to explain how to do Array.push, it doesn't mean that a TODO list is a good example on which to base a tutorial. It means your library sucks. If you want to show how great you library is, implement this: https://tonsky.me/blog/datascript-chat/ (demo: http://tonsky.me/datascript-chat/) and manage to describe it in as many words as on that page.
Back to the topic at hand.
The problem with Redux is: it's concept is very simple. The implementation is a poorly specified DSL with isolated parts that barely fit each other, if at all. And even with all the simplifications it can barely manage to make a TODO list with happy paths.
Here's where it all falls apart:
- It looks like I can't use async functions in `createSlice`. I must use a separate `createAsyncThunk`. Because reasons. Because in a world where every single app needs async data fetching "if you want to have async logic interact with the store by dispatching or checking the current store state? That's where Redux middleware come in.": https://redux.js.org/tutorials/essentials/part-5-async-logic
So, suddenly, it's not "we've simplified everything", it's the same old: we need thunks, and middlewares, and whatnot to do data fetching.
Note in that page how `createSlice` (which is an action creator with reducers and stuff) and `createAsyncThunk` (which is an action creator for which you have to provide a store separately, and reducers to that store separately) don't ever fit with each other except through additional boilerplate code. This is especially visible in `features/posts/postsSlice.js` towards the end of the page
And why does everything need a dispatch anyway? Can't everything already be wrapped in a dispatch so that you just call a function?
- There's almost nothing about errors that will inevitably pop up.
Almost no part of the tutorial ever shows how to deal with errors except the "ah, it will magically dispatch an error message to the store, we store the message on the store".
What happens if I run into an error in a "reducer" (which is reducer-slash-action-creator) in `createSlice` and want to dispatch some action? Oh, wait, you can't dispatch actions from a reducer.
What happens if I run into an error in an action created with `createAsyncThunk` and I need to not only return an error, but dispatch some other actions?
- All this is just so, so badly specified. Everything is hidden behind "detailed explanations" that actually try to explain what's going on
For example, the fact that createAsyncThunk magically creates `.pending`, `.fulfilled` and `.rejected` on an action is not explained except sort of in passing.
And there's suddenly a magical `unwrapResult` function that's not even mentioned anywhere. What is the result of an action? It's not in Part 1 or Redux Concepts and not in Part 1 of Redux Fundamentals. I very quickly scanned the rest of the War and Peace novel, and I can't say I found it.
And there's more. I just can't quickly read and grok the 20 volumes of dense prose just to figure out how to push data into an array.
"Async logic and data fetching are always a complex topic." Nope. It's not. Redux makes it a complex topic.
---
So yeah, it all very quickly devolves into the very same "15 different files for every small action". You may collocate some of them in a single file, but that doesn't make them even closely related to each other in any meaningful way.
That's why I said, "I don't know where the sweet spot of Redux it: it's an overkill for small apps, it's unmanageable in big apps."
And it's not all bad. The biggest step towards simplifying Redux have been hooks. `useDispatch` and `useSelector` are really good. Even though we have to admit that they are solving the issues that Redux introduced in the first place :)
Your comment really cuts through to the truth about Redux, and gave me flashbacks to my “what the hell” moment the first time I needed to perform a fetch, and the subsequent painful mentoring of a junior dev who just couldn’t get it. Two other junior devs quit because they inherited a React codebase using RX. I couldn’t really blame them.
To be fair, you're criticizing Redux more than React. And even then, you're criticizing a specific pattern of using Redux: redux-toolkit melts away all the boilerplate you're talking about, and you could use something else than Redux if you wanted.
I agree that React folks (me included) tend to reach out to Redux much too soon, though.
100% this, I often find myself getting tripped up when reading people's complaints about the JS / React ecosystem until I remember that TypeScript and the ESLint (and typescript-eslint and eslint-plugin-react) recommended config isn't standard in everyone's projects. It saves you from a ton of these headaches.
Not that the answer to a complaint of a complex toolchain is more tools in the chain, but it's a wonderful development experience once you develop a collection of tools you really like.
I’m glad you’ve found something that works for you, but the fact that each react dev has their own complicated way to build a web app is exactly what made me leave react.
Yeah I've moved to this and it is working so far. Hooks manage the data and etc, UI components are almost exclusively just UI that get some props.
The UI components all have an else render if for some reason they're missing something / it avoids a crash (along with other easy to see / figure out protections)
Of course, what you see is just a tip of the iceberg, having implemented a nice admin and moderator dashboard, keyboard shortcuts, animations, 12 different color themes (in light and dark mode), and more. Svelte just made it all so easy to do. Having done a good bit of React and Angular professionally, and tinkered with Vue, I can safely say (for now) that all I want to do is Svelte. The rest just seem so heavy handed in comparison.
Self promotion: I also run a YouTube channel where I'm slowly building out a Svelte app with Rails API backend. If you're interested in Svelte, please check it out: https://www.youtube.com/user/iamdavidwparker
Same here. I've used Svelte on two reasonably hefty personal project sites[1][2] and was pleasantly surprised by how easy they were to build and (now) maintain.
The one issue I had with Svelte was Sapper - I didn't get on with it (it wasn't a good fit for either of my projects). SvelteKit was released in beta a couple of days ago - I've no idea how much that changes things. I'm hoping I don't have to relearn a whole bunch of stuff to use the new shiny.
1) It doesn't makes much sense to use onMount() with listenForAuthChanges(). Just modify the currentUser writable whenever onAuthStateChanged() has an update. Now simply import and use $currentUser in your components.
2) You don't need to use currentUser.subscribe() from your .svelte files as you can use the reactive $: notation for this. You can, if it makes more sense to you, but I wouldn't say it's idiomatic.
The svelte approach of implementing better paradigms using compilation rather than using abstractions just seems infinitely better, and a better way to scale up a program in general. I do wonder why this idea never caught on; I think Alan Kay was investigating building a programming platform based on this idea but that's just academic.
I think it's an academics vs practitioners thing. Rich is willing and can use Svelte and Svelte/kit in actual production workloads in NYT. Kind of similar in how most Rails abstractions are pulled out from Basecamp's implementation. Doesn't hurt that Rails HEAD is directly used (in % deploy) by Shopify etc too.
Because these tools were made in a time where people were using libraries like jquery. People were building js apps by including libraries directly in the header. Before webpack, and even gulp, when having a build process was a novel thing in web dev.
I am not sure I understand. In the `MenuSelect` component you can declare `export let users: Array<User>;` and then pass into `users` anything that has the `User` interface.
I assume you don’t like invocation? That’s not the point here. In svelte you can’t* define type variable that’s bound to the same type in whole component. I can’t enforce that properties `items` is `T[]` and `selectedItem` is `T`.
I'm writing about <MenuSelect<User users={..} /> and not about Svelte. I don't know anything about Svelte except what I just learned in the article. I just don't like the syntax of <MenuSelect<User> /> and favour <MenuSelect /> instead, because Typescript will inherit it's generic type from users={...}
I think it's a misunderstanding that Svelte is fast because it is "compiled" into imperative DOM updates. This is partly because the documentation says so, and the early blog posts emphasized this aspect.
Compilation actually has nothing to do with it. Svelte is fast because it doesn't do all the Virtual DOM stuff, and the DOM diffing. You could very well build Svelte as a library without the compiler, and it would be just as fast.
But the compiler helps a bit with the ergonomics - it's cleaner to do count+=1, rather than setState({count:count+1}).
I believe infernoJS complete with vdom still matches and even exceeds on most metrics.
Last I checked, svelte compiled different code paths for every single component.
This means that most DOM manipulations in real-world applications (not micro benchmarks using the same components repeatedly) won’t ever optimize in Svelte. Meanwhile, the vdom code runs constantly and is hand-tuned to help ensure deopts don’t happen. Likewise, potential optimizations to reuse old nodes and other “garbage” are much easier to implement with a unified DOM implementation. Finally, once you implement the vdom once, you don’t do it again. With Svelte, your code is recreated for every component. This may give smaller component in the short term, but there’s an inflection point (and it’s in the small-medium range) where svelte starts shipping more code for the same work.
I stumbled upon this article a couple of days ago when it was featured on the last issue of Javascript Weekly (19.3.21) and thoroughly enjoyed reading it. The points made in this piece are soundly argued while never hiding the fact that they're ultimately based on one individuals perspective.
Couple of days later and in line with my usual HN-reading MO (and not remembering the title of said article, it is quite generic) I'm looking at the headline of this post and my mind immediately goes «Yeah, I definitely want to read those comments, if only for shits and giggles». And boy was I not disappointed:
It starts off with «I love X because I hate Y», continues with Y implicitly being understood as a function of «Y + 3rd party Z» (and what's not to hate about bloody Z as if that's not a self-inflicted liability) and ends with library authors digressing into low-level technicalities, which, ironically, both X and Y are trying to simplify in the first place, so that ordinary product-shipping folks like the author of the original article and myself can focus on the task at hand: Getting shit done.
It really feels like everyone on here was just desperately waiting for a hook to talk about either their personal preference or their specific set of problem domains instead of adding value to an admittedly mundane but very concise piece of writing.
It probably speaks volumes about my naivety that, after all these years of digesting HN on a daily basis, I still somehow assume that both the subject matter and the tone of a given piece might actually correlate with the reactions to it. Then again, as I said, I came here for shits and giggles, so, if I'm perfectly honest, I kind of knew all along.
I came across Appwrite[0], when looking for open source Firebase alternatives.
It has a long list of supported OAuth2 providers [1].
It worked for me out out the box (DO docker image), and seems to be progressing well. They have some beta Svelte libraries, and it was simple enough to integrate for my uses.
My experience is that if you use it how they want you to use it (as a separate page hosted by them) then it’s simple and easy to use, but if you want to embed it they go out of your way to make your life painful. We took the latter route since we care about our UX and probably would have been better off if we just rolled it all ourselves.
Do you need social logins? If not (and you just want email/password login) then implement it yourself. Auth isn't hard, and Firebase Auth doesn't provide you much in terms of managing roles/permissions or anything like that.
Authentication/Authorisation (AuthZ) is hard, brother. Implementing what you think looks like AuthZ is easy, but making sure you've got all possible avenues covered is hard.
Getting AuthZ correct and having everything covered is hard.
Security is hard.
Source: see the entire field of security and all the companies, products and services that have been invented to try and solve this problem.
Have been using firebase for years without significant hiccups. I was wary at first but I'm glad I moved over it. Support is strong if you have a paid plan and their documentation is fenomenal.
Can promise good support over at Clerk ;) We are new, and are trying to provide an easier, more comprehensive authN/authZ/user management solution. We're also taking requests for individual use cases as we're ramping up!
No mention of Typescript. You'd be mad to consider writing a significant app without it, and React has really great Typescript support - even templates are type checked properly thanks to JSX/TSX, and basically all tools support JSX these days.
The one place where react typings are disappointing to me is that all elements are typed as JSX.Element, meaning you can’t assert a specific type of child element. Usually not a big deal but would be useful in a number of cases.
> thanks to JSX/TSX, and basically all tools support JSX these days
I haven't used TS and Vue together, but Vue 3 seems to have been written with TS, as well as having supported JSX for quite a while. What are the limitations?
I never got the appeal of state management in React. People now seem to rave about Svelte, because you can just do 'count += 1'.
But you could do exactly this already in Angularjs (which, just like Svelte, had a template language).
Ok, Angularjs worked different under-the-hood than Svelte. It was a dirty checking loop instead of compiler-generated change indicators, which didn't scale as well. But other than this scaling issue, the ergonomics were very similar.
But in the meanwhile the React folks started this whole idea that this mutable state was a bad idea and people seemed to blindly believe the React way was going to be the silver bullet. The widely acclaimed Dan Abramov launched Redux, one of the most cumbersome, boiler-plate-heavy state management systems I've ever had the displeasure of using. And with react hooks there are more performance pitfalls than there ever were with Angularjs.
So now everyone seems to realize that they lost quite some convenience with React. React had a good run, but it's time to move on.
Angular does not use decorators to manage state. It uses decorators to create compile-time instructions for the dependency injection system to wire components, services, and directives together (I'm simplifying a bit here). Should TypeScript ever drop their decorator implementation, this could all be fairly easily changed to do it with functions instead:
export class AppComponent { }
export const appComponent = Component(AppComponent, {
template: '<h1>Hello World!>/h1>
// other compile-time instructions
});
How you want to approach runtime state has nothing to do with decorators.
And like I said, react promised immutable state would solve a lot of problems, but it turns out you lose a lot of convenience as a developer while not gaining that much.
What perf issue have you spotted with hooks? My biggest bugbear with them is just the sheer cognitive exhaustion that comes with figuring out "when" stuff is happening -- especially if you're implementing a bunch of your own useEffects. I remember just sitting there bemused by the complexity -- and the difficulty of debugging ostensibly simple state changes. I (truly!) miss the good old days of intuitely doing count+=1 in a mess of procedural DHTML... so for that reason alone Svelte is a breath of fresh air.
For example passing down a function which is not memoized can trigger a bunch of non-obvious side effects lower in the component tree when your component re-renders. I feel like with hooks/functional components I need to think way more about stuff like that.
Which comes down to your point: cognitive exhaustion. The promise of hooks and functional components was that it would make things 'easier to reason about' (among other things). But we all know now that this is just not the case in practice.
Svelte I think is the "more technically correct" approach, but React 16+ is good enough, concise enough, and it has thousands of community packages already. Svelte still needs way more buy-in for it to be a serious option at our company. We actually implemented a small part of our app in Svelte, but moved away from it because Svelte is still sort of a tinkerer's tool[0]. I really hope it gets more adoption, though. Until then, we're going to be using React 16+.
I really do not understand the appeal of Svelte, it seems to be making all the same horrendous mistakes as Vue (bad templating/DSL that shoehorns control flow and bindings into DOM representation, esoteric lifecycle hooks etc) and the only advantage it clearly has over other frameworks that I can see is simple components can generally be written more concisely.
Are there actually any tangible benefits to using it over other frameworks, or is this all just a heap of people getting excited because it's "new and shiny" (compared to React, Angular etc)?
- Instead of virtual dom + diffing, svelte applies direct DOM manipulation exactly where needed
- Smaller code size. No loading of general runtime library, and I've heard people argue that each component(or page) has its own runtime (I may be wrong in the details)
- Many common tools are built-in, like stores and animations, and since there's no general runtime, if you don't use a particular tool, it doesn't get included
- For beginners, the tutorial is a blessing, they can pick up the framework while learning Js on-the-fly (I'm doing this with my nephew)
Smaller code size only applies for small projects. Preact is like 4kb (I believe you could print off the minified source on one side of a sheet of paper) and most full vdom implementations are still only 40k or less over the wire.
Once you have the runtime, it’s a fixed size. In Svelte though, every new component must carry not just the description of changes, but also reinvent the wheel by reimplementing each time. How many components do you need to reach 4K or even 40k? Even a small app can easily reach the size inflection point.
Also note that inlining code works differently in a JIT. Your code must run hundreds of times to be optimized. In a vdom, that is done almost instantly for all the most critical render code paths. In contrast, infrequently used svelte components are never going to optimize. This has the effect of making svelte look very fast in micro benchmarks which run the same few components repeatedly, but doesn’t reflect your performance when the user opens up something different and winds up back in interpreted mode.
Preact is a nice middle if you’re concerned about garbage. In addition to being tiny, it diffs against the DOM directly instead of using a vdom.
I would argue requiring your whole team to learn a new framework-specific templating language and patterns is not simple, compared to using something that is plain JS/TS which the whole team already knows. It also complicates hiring, as programmers who know JS/TS and can pick up React/Angular/etc are a dime a dozen, in contrast to trying to hire programmers to work with a niche framework which has a lot of domain specific knowledge.
Great comparison writeup! However, I do feel some of the points made are weak and seem to be rushing to conclusion.
In the React hook worker code you do not really need to use the `ref` and you can do something like:
```
function useWorker(currentUser, setCurrentPom) {
const [worker] = useState(() => new Worker(workerURL))
useEffect(() => {
const onMessage = (event) => {
if (event.data.name === 'tick') {
setCurrentPom((currentPom) => ({
...currentPom,
secondsRemaining: event.data.counter,
}))
} else if (event.data.name === 'start') {
// More branches removed here to save space...
}
}
worker.addEventListener('message', onMessage)
return () => {
worker.removeEventListener('message', onMessage)
}
}, [currentUser, setCurrentPom])
return worker
}
```
The reason it works is that `useState` allows you to lazy load something.
You mention two points which make svelte easier, but I would challenge you on those:
> We don't have to keep the worker in useRef, and can just assign it to a variable.
As per my thoughts above you do not need useRef, in fact you just separated the whole worker logic into a separate function which gives us separation of concern and better readability.
> We can pull the event listener code out into a function more easily, as that function won't then become a dependency to a useEffect - at which point we will have to wrap it in useCallback.
I am not sure I fully understand this. But if you are talking about wrapping the event listener code into a function, you can do that and you do not need to wrap it in a callback. Though, I am not sure what we gain out of abstracting the event handling part.
Svelte has made the classic mistake of creating a template-based framework, which inevitable leads to a DSL, then to a miniature programming language, then to a nightmare of incidental complexity. React won for a lot of reasons, but near the top of the list is JSX.
JSX is not a templating language, it’s just syntactic sugar for plain JavaScript. I never have to wonder what new features have been added to JSX, I never have to struggle with extensions to JSX that some 3rd-party library depends upon. Template-based frameworks are akin to Lisp reader macros, and this is especially true for Svelte in that it is a compiler. But reader macros will kill a language’s ecosystem quickly, in that they break composability. JSX was a clear winner over Angular’s directives for exactly this reason.
Svelte has some cool ideas, but regressing back to another Handlebars or Mustache isn’t one of them.
We are actually going the other direction. Currently I am experimenting with making a Blazor/Liveview (HTML over wire) runtime that can support many programming languages. And a GUI builder.
The UI builder and libraries are still heavily WIP. Previously we were focused on making a VBA interpreter.
For the HTML-over-wire part we are experimenting with a protobufs-based protocol so the backend runtime can support as many languages as possible. The most challenging part is that client-server UIs have fundamentally different assumptions about state, concurrency, and session handling compared to traditional desktop/mobile GUIs that operate on callbacks. Most of the design patterns used for your typical WinForms/Cocoa UI fail horribly in web scenarios (unless the entire app is running client side in WebAssembly) because state must be held in a DB, global variables are completely not allowed and multiple users may simultaneously access a service so any form of long-running loops or blocking code is very problematic. Serverless could help here but you will need long-running serverless containers. The current pricing models of AWS Fargate and Google Cloud Run are not compatible with this.
This is additionally complicated by the fact that many languages have async/concurrency "bolted-on". It is hard to integrate a WebSockets server into the language if there is already another event loop to deal with. This is especially problematic for feature-fragmented languages like Python where async is a relatively new concept. (See https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...) Go is basically the gold standard here. Overall, we are still working on how to build the language-agnsotic backend SDKs without too much duplicate code. If you see similar systems like Streamlit and Plotly Dash, they avoid the problem entirely by making proxying the default way of embedding in third party applications. The integration is not as tight as traditional UI frameworks but avoids many of the problems with state management.
For deployment, the Blazor/LiveView/HTML-over-wire format would also require CDNs that are as close to the end user as possible for lowest UI latency. Think Zeit/Vercel or Fly.io.
There's a mailing list link below where you can be notified for when we launch:
Svelte gives us a host of possibilities because of it being compile time. Its the perfect UI language.
Think about it. You can write anything in the script part. Lets say you have a Go UI framework. Write Go code inside script tag and use the template to produce UI elements for TUI apps, for desktop apps without even working hard to manage state, manage UI element paradigms. The simple reactive style of programming can be used. Of course a different compiler can be used. But the possibilities are open. With React and stuff that's kind of difficult. You will eventually end up writing original Go code.
And currently you can compile Svelte components into React/Preact etc. Its not production quality. But its possible. (Svelte2TSX makes it possible)
Since svelte does a lot of compile time stuff, I would like to mention Routify, which is compile time SPA routing. For me it worked awesome to accomplish projects with many routes and the need for hash based routing.
I work with Vue every day at my day job and I spent about 200 hours on the side building an online Svelte course. I really enjoyed the syntax and readability of .svelte files. Local dev environment was a great developer experience and the lack of boiler plate was nice coming from a Vue background.
I built an SPA with an Express backend and my biggest complaint was the lack of an official front end router. The community based router libraries had lots of issues and quirks and I didn’t want to lock into using Sapper at the time. Svelte itself was rock solid though and I look forward to trying Sveltekit (think Next.js for Svelte) in the future.
I have been maintaining some Svelte components. But as many of them here, my profession is in React as of now.
One of things Author didn't directly point out is about JSX. I mean you can build JSX of a component incrementally in your component code. But that's something I am missing in Svelte.
It's actually very simple. React devs choose to write code in code. People of other frameworks write code in code and templates, which, to react devs, is complexity.
How's unit testing in Svelte today? That's the main worry I have with template-based systems (well, that and TypeScript support, but I hear that that's quite OK now).
There’s svelte-testing-library [0] which follows the same approach as the React flavor. It emphasizes testing the app “as the user would use it”, which boils down to rendering components and inspecting the DOM. They intentionally don’t give you visibility into the internal state of the component, which can be weird to get used to if you’re used to doing things like “click the button; assert that state isClicked became true”, but it tends to result in less brittle tests.
Thanks, that looks pretty good, and testing-library is excellent. I couldn't find it in the docs, but do you know how this "renders" the Svelte components - in memory using e.g. jsdom, or by firing up a full-fledged browser? (The former would be highly preferable.)
It's not great as other people have mentioned. But I think storybook is a good middle ground for testing (it might be a stretch for others to see it that way) and Svelte has awesome support for storybook.
Storybook is neat, and very useful for automated smoke tests, but I wouldn't want to write extensive unit tests that rely on firing up an entire browser - far too flaky to my liking.
What are the downsides or criticisms of Svelte? This whole thread is like a giant bandwagon. Typical of JS crowd. 3 years later, everyone is going to jump on the next one.
I went with Svelte for my first side-project[1], having never touched a frontend framework or used Javascript before.
The experience was actually really nice. Obviously I don't know how it compares to other options out there, but it was a pretty enjoyable experience overall. I felt like I was able to do exactly what I wanted and wasn't missing anything.
How come I rarely see mention of Aurelia when discussing JS frameworks? Aurelia 2 will be out soon, and I find things well thought out even in v1. You basically write just html and pretty much plain javascript. It is batteries included with DI/router/Event listener/validation etc.
Is it necessary to use a worker for the timer? If you just record the start time, then inconsistent setTimeout calls aren't a problem because each call calculates the duration from start time to current time. Or am I missing something?
i'm just getting into front-end programming, and my (perhaps unfair) impression of react was that it was "bloated", so i stayed away from it. i've played a tiny bit with svelte and loved it though, i'm hoping to get a PWA up and running with it and invest a lot more heavily into learning the ecosystem if that's a good experience.
which is technically simpler -- you're not constructing a new anonymous function. But if you wanted to extend the code (to say, add a console.log), you'd need to do the top version, which already has the braces.
It's up to your judgment of what's clearer and likely to be more maintainable.
That may be visually simpler, but if onMount were to start passing a parameter to its callback in a future version (for whatever reason) then that will be passed to listenForAuthChanges. Using the anonymous function means that wouldn't happen.
That should work in the provided example, but in general `fn(cb)` is different: you lose the implied `this` binding unless you do `cb.bind(something)` beforehand.
There's also some unexpected trouble due to potential extra parameters. Consider the output of
['1', '2', '3'].map(parseInt)
gives:
[1, NaN, NaN]
So yea, fn(() => cb()) is the same as fn(() => {return cb()}) – just syntactic sugar when the anonymous function has just one statement. but fn(cb) is semantically different.
Most common place IME you end up needing to use it are when assigning functions on a class as event listeners: if you want to be able to remove them, you need to use the same value in `addEventListener` as `removeEventListener`; and if the function uses other properties of the class, you must use `.bind(this)` to correctly set `this`.
It may help to think of it like this. This code
object.someFunction(a, b);
is syntactic sugar for
someFunction.call(object, a, b)
So with most usages of functions, the `this` is implicitly defined. The trouble comes when you start passing around references to functions.
const fn = object.someFunction;
fn(a, b); // throw `this is undefined` error if function accesses this
Here, `this` was never defined. So you need to do one of:
const fn = object.someFunction.bind(object);
fn(a ,b);
// OR
fn.call(object, a, b);
The last thing you need to know is the difference between arrow functions and classic functions. Arrow functions _never_ have a `this` binding of its own. It doesn't make a scope. If the scope it was defined in happened to already define `this`, it would use that. Classic functions (`function blah() {}` or `const fn = function() {}`) do make a new scope, so they have a `this` binding, so if you wanted to access `this` of the outer scope you'd have to assign it to another variable beforehand
class A {
a = 1;
fn() {
const thisRef = this;
return function () { return thisRef.a }
}
}
With arrow functions, it's easier:
class A {
a = 1;
fn() {
return () => { return this.a }
}
}
>which is technically simpler -- you're not constructing a new anonymous function.
Not just technically simpler, it is a perf improvement over initializing an anonymous function every time onMount is called (which you have already mentioned, I just wanted to emphasize). A minor performance improvement at that, but still, small things add up. I agree with the entirety of your post, however, well-written.
Svelte is both succinct and powerful. I find this in contrast to React which is often baffling and incoherent. I say this as someone that has used React professionally for 6 years. They've changed their mind at least 3 times on the API and none of it fits together today. Hooks and classes don't mix, lifecycle methods are still out there and just as confusing today as they were years ago. Mixing state and functions. It's just a horrible bag of half-baked bad ideas. And it's not even batteries included.
Svelte has it all built-in. It has the equivalent of CSS modules. It has global reactive state and an incredibly simple state protocol that seems almost stupid if you're coming from Redux. You step out of your daze and realize you don't need all the rituals and boilerplate. The things I've seen built on top of that simple protocol can be incredible. It's not magic. It's just JavaScript.
I dare say that Svelte is a joy. But I hesitate to even leave this comment here. Because I know the architecture astronauts are listening, eager to sink their claws into this simple elegant new thing and fill up their Github profiles with soon-to-be abandonware cruft which they will use to pad out article after Medium article which will all pour down upon Hacker News like a shit typhoon. Prodding us to join their cult of Svelte+RxJS. Or Svelte Sagas. Or their random dogma they toss into a giant bucket labeled "Svelte best practices." And, of course, "Ten ways you are doing Svelte wrong."
I might be a little bit cynical.