Largely I agree with everything in this article on a factual basis but I disagree on the framing of the trade-offs. Two points in particular:
1. Before open sourcing React we extensively measured performance on real world applications like mobile search and desktop ads management flows. While there are pathological cases where you can notice the overhead of the virtual DOM diff it's pretty rare that it meaningfully affects the user experience in practice, especially when compared to the overhead of downloading static resources like JS and rendering antipatterns like layout thrash. And in the situations where it is noticeable there are escape hatches to fix it (as mentioned in the article).
2. I disagree with the last sentence strongly. I would argue that Svelte makes huge sacrifices in expressive power, tooling and predictability in order to gain performance that is often not noticeable or is easily achieved with React's memoization features. React was always about enabling front-end engineers to take advantage of software engineering best practices so they could level up velocity and quality. I think Svelte's use of a constrained custom DSL is a big step backwards in that respect so while I appreciate the engineering behind it, it's not a technology I am interested in using.
Even though I disagree on these points I think it is a well-written article and is a pretty fair criticism of React outside of those two points, and I could see reasonable people disagreeing on these trade-offs.
The secondary effects of React's popularity have been a significant loss in emphasis on HTML and CSS skills over doing everything in JS which often results in div-soup that is less performant and less capable. HTML and CSS not being first class-citizens as they are in other frameworks means developers simply aren't encouraged to learn them nearly as well as they used to.
Front-end frameworks should not be just for front-end developers. They should be understandable and accessible to people who are not JS first and who come from backgrounds that are not purely engineering. In my experience it is much easier to teach someone familiar with design and HTML/CSS how to explore a Vue or Svelte codebase than a React one, and the JS devs I have worked with who fully learn these libraries have not complained about any limitations compared to React.
In what ways are HTML and CSS less than first-class citizens in React? In my React codebases we've always written CSS (or SCSS) stylesheets, and components bottom-out in JSX (which is almost exactly just an HTML template with inserts)
In my experience the biggest barrier to (and most legitimate complaint about) React vs other frameworks is state management. It's always had special rules around that (even for experienced JS devs), and hooks turned that up to 11 (not without good reason, but still). While Vue at least has always had super simple state management: you modify normal JS values and the UI updates. Are you sure that hasn't been part of the divide you've observed?
(for the record it is also possible to retrofit React with state management more like Vue's (MobX, etc), but it'll be a compromised experience in some other small ways, and it may create some pain around some other library integrations that ship as hooks)
Then there is the template side of things where it is much easier to read a basic conditional or loop in Vue/Svelte for someone who doesn't know JS.
How much have you used Vue or Svelte? It is a significantly better developer experience for building HTML and styling it because it doesn't force everything to be written in JS. In React JS is the only first-class language. There is no built-in support for SFCs, style scoping, etc. Thus the awfulness of CSS-in-JS was born.
Hooks are pretty sweet but I think they should have launched with higher order hooks that used the same lifecycle as original React. That way you wouldn't need to think too hard about things like object identity (which I think is the state management issue you're getting at -- it wasn't a huge issue in the pre hook days)
I think this is the key take away.
React is great and offers a lot of tunability in terms of performance but to me it often feels a lot like writing low level code.
When I use something like Vue, Petite-Vue, Svelte, Angular (flawed but underrated) - it feels like using something high level (like an ORM) that is designed to take something difficult and map it to something human readable.
The relationship between my changes in code/state ergonomically translate across to the template and that leads to very maintainable applications.
In reality though, despite the endless stream of blogs telling you how to write React applications, React isn't prescriptive on how you write it. You can create "view models" with proxy objects to implement primitive data-binding in components and use Context to inject dependencies.
In fact I once wrote a compiler that compiled html template syntax into `React.createElement()`, and along with a supplied runtime I was able to use React as a backend for a reactive template renderer.
> Svelte and Vue approach to SFCs
I have personally found SFC to be super annoying given they are not optional and require custom tooling.
A single file is nice sometimes but I don't really mind if a component is split into 3 files (js, css, html) as long as they are associated with each other somehow. I just want to ensure that my view renderer requires the least amount of tooling possible so my project will stand the test of time and I can be self reliant for updates.
Can I use my own test runner? Can I pick my version of TypeScript? Can I setup my own eslint? Can I set up my own css preprocessor? Can I use custom compiler configuration (like using SWC)?
To me, the fact that React doesn't require a compiler (aside from translating jsx) is its greatest asset, and Vue/Svelte and Angular's greatest shortfall.
It's just a shame that React is so fiddly to work with
And disagree on HTML/CSS skill loss being a thing, or being proximately caused by React. And no it doesn't have any div-soup causative effect either.
I get that templates are simpler for non-coders. Unfortunately, in all but the simplest cases the cost-benefit is towards the React model as you are just "using the platform" to build your views - ie, JS and not some arbitrary DSL.
But in my experience, React’s openness to expressive experimentation is not a net positive for maintainability, productivity, or quality. There are lots of footguns, and bad ideas embraced by the ecosystem. HoCs were a terrible idea, and were replaced with an even worse idea, hooks. Compared to that, Svelte’s reactive statements and stores are a bliss.
Arguably, it’s because React has experimented and made those mistakes that Svelte has been able to focus on a constrained DSL that mostly supports the features that are actually a good idea.
First, I think a mistake the react team made was not exerting more control over the ecosystem. I think a lot of questionable content marketing pieces ended up as “best practices” which we are still unwinding as a community.
Second, I totally buy the idea that there are multiple personas and that different tools speak to different personas. The sustained popularity of constrained dsls among subsets of the frontend community speaks to this.
I've been working daily with React for almost 6 years now. Another 10+ years of web development before that. And I have yet to see these benefits be realized.
It's not easy, at all, to "optimize performance using memoization" in React, best practices are mostly tied to JSX/React and its tooling, not general software engineering, and they change yearly based on the latest version and current third-party library trends. Velocity and quality are both hurt. Your best bet is to use an all-in-one framework like next.js, which delegates most of the pain to the authors - but not all of it.
The big improvements that React brought to the mainstream were componentization, and popularizing declarative rendering. I think we have better options today.
Svelte does not limit expressiveness in my experience, in fact it makes it way easier to do what you actually want to do, without contorting yourself to the framework. It's liberating. This is a viewpoint I hear frequently though, so it definitely depends on your personal path as a developer and what you've been exposed to.
Mobile phones have gotten faster, and so have mobile js improvements, so I wouldn't be surprised if it's negligible overhead these days.
Also, there are two types of performance: how fast code runs and how fast a team can maintain and extend code. Does one optimize for running code or for building code? React won because it optimized for the latter. And thank god, because who wants to mess with digest loops and magical DSLs?
Personally I don't use Svelte, Vue, Solid etc simply due to the (lack of) library support compared to React. For example, I wanted to do something in 3D the other day and reached for react-three-fiber, there simply isn't something comparable in the non-React world.
Respectfully, that is because you dont _need_ anything other than ThreeJS in the other frameworks.
I do find it interesting that it says it performs faster due to reacts scheduler.
This came out a year ago now.
https://svelthree.dev/ https://github.com/vatro/svelthree
Svelte, Vue, etc. all have plenty of awesome libraries - you just need to be aware of them.
Could you (or someone) characterize things that are hard to do in Svelte?
This is limiting when designing generic Component-APIs.
As an extreme example: In React, I could trivially define a tabbed interface by mixing strings, JSX, and components, without imposing any DOM-structure. The component that reads this definition could use it to build a tabbed-view on mobile, and a master-detail-view on desktop. ...or it could build a table of contents. When defining the tab names, I can mostly use strings, but fall back to JSX if necessary. Importantly, the API is completely independent from the implementation.
const tabs = [
{ name: "Tab 1", icon: <img ... />, content: MainTab },
{ name: <>Tab with <b>bold<b/> text</>, icon: <MyIcon ... />, content: SecondTab },
]
return <MyLayout tabs={tabs} />
[1]: https://github.com/sveltejs/svelte/issues/5381I would say they all can be fast. But try browsing the web on a low end Android device and tell me all sites are fast. To my mind the differentiator is how easy a framework makes it to shoot yourself in the foot. And React makes it very easy to re-render a huge swathe of your app when you've only changed one tiny element. React also needs to hydrate every element even when it isn't ever going to change, which usually involves parsing some JSON payload for props on page load.
None of this is world-ending stuff. But it is very easy to keep putting wonderful, carefully crafted components together and not realise the entirety of what you've made is getting slower and slower over time.
That is no longer true with Server Components https://beta.nextjs.org/docs/rendering/server-and-client-com...
It is not unusual at all to find some "simple" UI update causes the render() method to be called 20 times.
You might blame the application developer for this but other than "keep all the state at the top level of the application and pass it down in props", React doesn't provide a systematic answer for handling state in apps if data is flowing up, down and sideways. There are a number of half-baked libraries such as Redux, MobX that maybe help some of the time but frequently make very simple application logic very complicated to write. When I started writing high-complexity SPAs circa 2005 or so I realized right away that you had to be very systematic about what happened when an AJAX call returned and where the data goes, something the industry still hasn't entirely learned.
It is possible to make React applications work right but I think people work harder at it than they have to and there is a lot of reciting shibboleths that people don't understand (hooks for one thing) and the cost of it is the render function getting called over and over and over again. But maybe it is not a bug but a feature for the advertising supported web where every rerender and layout shift creates a chance you'll accidentally click on an an ad when you are trying to click on something else. Most studies seem to show that psychologically normal people of normal intelligence in possession of their faculties never choose to click on ads and maybe Google's whole business is driven by accidental clicks caused by layout shifts and doing something about those layout shifts would put them out of business.
The built-in React way of doing that is with context.
I agree. In fact this goes beyond frontend frameworks. One should apply the same approach to all methodologies and practices: OKRs, TDD, Agile, etc.
When framework/methodology is being sold to you, people talk about all the wonderful properties it has. But what you should really be care about is how easy it is to misuse and what happens when it does get misused. Because, trust me, it will get misused.
One of the most important things about particular technology is whether it lands you in a Pit of Success: https://blog.codinghorror.com/falling-into-the-pit-of-succes...
Holy smokes, that's more like a straw kaiju than a strawman. Obviously slow sites exist. That has almost nothing to do with the inherent overhead of recently created JS frameworks.
we don't live in the same universe. even with powerful computers, browsing any friggin modern website is an exercice in pain and frustration, everything, literally every interaction is slow when you compare to the average desktop app
If we are going to say things like "React is fast" then it needs a further clarification - fast compared to what?
Are we comparing it to C, jQuery, Angular, Pure Javascript, or a Commedore 64? Because it doesn't make sense to say it is fast if there isn't something to compare it against.
(In reality, I suspect it is only really "fast" if you compare it with something slow).
https://www.reddit.com/r/bugs/comments/rj0u77/reddit_redesig...
Although I also think it is fault of react partially. React don't really have a proper guideline about how to not write page like this.
It’s like thinking that a faster car or a bicycle could be faster in a city with bad traffic light logistics. All of Svelte, React, Vue, jQuery, DOM are equally visibly fast until you attach these 10 megabytes of /metrics-n-spyware/**/*.js.
When you say "fast" - I assume you mean runtime speed, not time to market/developer speed? Because FTA (quoting Pete Hunt in regard to React itself):
> Just like you can drop into assembler with C and beat the C compiler, you can drop into raw DOM operations and DOM API calls and beat React if you wanted to
Even the React people acknowledge that just working with plain-old Javascript is going to beat React (or Svelte, or X, Y, Z) is going to perform better at run time, they're just offering to speed up the development cycle - always with the tradeoff of runtime performance.
And what about user with a low end Android machine? Not lagging the ui isn't helpful here. Because it still need seconds to render the whole thing.
JSX and VDOM were at one time necessary (or at least helpful), but Web Components and Tagged Template Literals can do everything React does, only better and with less overhead (in both the developer’s mind as well as the computer’s runtime). I say that as someone who learned and taught bootcamps with React, and has yet to dive too deeply into lit-html and LitElement.
There's a vanishingly small number of applications where it's really going to make a difference. Use what your work uses, or if personal project what you like. The more I use React and co. the more I feel like it's all the same thing.
Developers (and especially deadline-conscious managers) keep saying this, but their web sites keep slowing down my computer to a crawl. As a consumer, I really wish that development teams paid at least some attention to performance.
Having implemented virtual DOM natively in Sciter (1), here are my findings:
In conventional browsers the fastest DOM population method is element.innerHTML = ...
The reason is that element.innerHTML works transactionally:
Lock updates -> parse and populate DOM -> verify DOM integrity -> unlock updates and update rendering tree.
While any "manual" DOM population using Web DOM API methods like appendChild() must do such transaction for any such call, so steps above shall be repeated for each appendChild() - each such call shall left the DOM in correct state.
And virtual DOM reconciliation implementations in browsers can use only public APIs like appendChild().
So, indeed, vDOM is not that performant as it could be.
But that also applies to Svelte style of updates: it also uses public APIs for updating DOM.
Solution could be in native implementation of Element.patch(vDOM) method (as I did in Sciter) that can work on par with Element.innerHTML - transactionally but without "parse HTML" phase. Yes, there is still an overhead of diff operation but with proper use of key attributes it is O(N) operation in most cases.
And while your statement makes intuitive sense regarding performance, actual measurements show clearly that idiomatic Svelte (and other modern frameworks) routinely beat VDOM-based efforts handily in their idiomatic cases and often even when folks jump through the performance optimization hoops needed for VDOM.
VDOM is pure overhead. Better than manually aligning writes before reads manually a la 2010, but noticeably worse than the current crop of compiled offerings.
But, for example in Sciter, vDOM works in [web] component cases that are similar to Svelte:
class Beers extends Element {
bottles;
render() {
return <span .bottles>{this.bottles}</span>
}
set value(v) {
this.componentUpdate({bottles:v})
}
}
When you will do document.$(".bottles").value = 12;
it will update only what is needed. Pretty much Svelte style but with the convenience of vDOM.When I manipulate the DOM I try to create the entire structure in a fragment and the use .append(...) only once.
element.append([array of Elements]);
is in magnitude of times faster than for(const el of [array of Elements])
element.appendChild(el);
so yes, it helps to improve situation.But think about updates like this:
element.patch(<p multiple={n > 1}>There {n > 1? "are": "is"} {n} bottle{n > 1? "s": ""} of beer on the wall</p>);
Here you need to update (or not) as the attribute as text nodes. You need some transactional mutation mechanism.My naive take on this is that browsers have overall gotten a lot more consistent with the layout-paint-composite loop, and it's not worthwhile to swap out all your appendChild calls with fragments. On the other hand, making sure your all your layout reads (.clientWidth) are batched before the layout writes (appendChild) is much more important (fastdom)
edit: something like documentFragment/append(...children) would help guarantee the layout trashing addressed by fastdom
Why can't they also use innerHTML?
Meaning, they could define a cost function where they deduce that it's cheaper to use innerHTML on a potentially larger than necessary scope if the alternative is >some_threshold for modification API calls.
Wanting to use it on a new project soon. Love it over some complicated and bloated Electron solution.
1. BeginUpdate stops a control from repainting itself and that is what browser is doing already - no painting happens at the moment of JS execution. So primitive "postpone painting" does not really help.
2. element.update(callback) or DOM.mutate(root,callback) shall be a single method - no one wants EndUpdate() calls to be skipped because of errors thrown and the like.
If we're rethinking the web stack I'd advocate for htmx with wasm-based web components for more complicated stuff like if you needed to polyfil in some new image format, or run a terminal emulator, or do webrtc calls with your own fancy custom noise reduction algorithm.
Yes I realize I'm essentially advocating for jquery with java applets, but it could really work this time! (I think a lot of the issues originally were political)
Make htmx like attributes part of the HTML spec, keep working on web components. Still don't know why web components haven't taken off.
How is WASM less accessible than Javascript? Are crawlers parsing minified and obfuscated Javascript sources and deriving meaning from them in a way they couldn't from WASM code?
Don't know about screen readers. I'd be surprised if they weren't using the live DOM, though.
Crawlers are based on consuming text.
HTML is text. Sites that optimize for SEO also use JavaScript to provide SEO context. The specific standard is called JSON+LD; pretty much any site that you use where SEO matters has JSON+LD, RDF-a, or Microdata embedded in the HTML.
You can see these structures if you use the Schema.org validator: https://validator.schema.org/
Try plugging in a URL like Reddit.com and see for yourself. On e-commerce websites, it's a *must have*. For example, try this Amazon page: https://www.amazon.com/dp/B09V3GZD32.
TL;DR: crawlers are parsing RDF-a and Microdata in the HTML or JSON+LD embedded in `<script/>` tags.
You can learn more about it here: https://developers.google.com/search/docs/appearance/structu...
My point was that yes, VDOM has overhead. But we accept it as a tradeoff for app development in the name of DevX.
Anyway, another idea is to ditch the entire DOM and render on the canvas.
So the vdom approach of processing all the static parts of a template during a diff is just extremely wasteful, especially for fairly common cases like conditionally rendering a node before some static content.
Ideally you already know what changed, and can just update the parts of the template that depend on it. In JS that typically requires a compiler, complexity, and custom semantics (like Solid). But you can get very, very close to that ideal with plain JS syntax and semantics by capturing the static strings and the dynamic values separately then only comparing the dynamic values on updates.
This is what we do with lit-html and why I think tagged template literals are nearly perfect for HTML templates.
With tagged template literals, an expression like:
html`<h1>Hello ${name}!</h1>`
is passed to the `html` tag function as an array of strings `['<h1>Hello ', '!</h1>']` and an array of values: `[name]`, and the strings array is the same every time you evaluate the expression, so you can compare it against the previous template and only update the values in the DOM if it's the same.It's a really efficient way to render and update DOM with a pretty simple conceptual model that requires no compiler at all - it's all runtime. I think it's straightforward and powerful enough to be standardized at some point too.
Vue 3 already just render the whole static contents to string in this case. And this is one of the selling point of vue 3.
It just happily serialize a big chunk of static template into string and create fragment on runtime with it. So client don't need to create static elements one by one. Besides that, it also mark that static content as "just don't diff it, it won't change", so runtime won't even try to diff it.
Switch to the js panel and you will realize that it already serialize the whole v-node thing into html on build time.
Isn't this a core idea underneath the https://fresh.deno.dev/ "islands" and I believe the https://astro.build/ framework when they confronted issues around hydration/SSR?
https://www.patterns.dev/posts/islands-architecture/
Clearly there's some overhead via the vDOM and simply using React-like templates when building large blocks of HTML. But if the bulk can be rendered server-side that overhead isn't an issue. So you can address this by simply reducing the data binding to the bare minimum of HTML that actually need to be interactive.
That way you can use the same templating and component systems app-wide but the default is still static-first.
That said - the Cons section notes: "The architecture is not suitable for highly interactive pages like social media apps which would probably require thousands of islands." But at that scale there's often far more performance concerns than vDOM vs compiler vs [some better optimized templating system], where the benefits aren't as straightforward (as linked below https://twitter.com/dan_abramov/status/1135423065570127872).
> Marko will recognize that the template fragment produces the same output every time and it will thus create the virtual DOM node once ... Rendering a static sub-tree has virtually zero cost. In addition, Marko will skip diffing/patching static sub-trees.
> Marko will also optimize static attributes on dynamic elements. [Static] attributes [are] only created once and [are] used for every render. In addition, no diffing/patching will happen for static attributes.
Element.update(function(updateCtx) {
updateCtx.setInnerText(this, "new text");
updateCtx.setAttribute(this, "title", "new title");
...
});
This has two benefits: 1) transactional update, 2) for contenteditable scenarios it can group DOM mutations in atomic undo-able action.But I've discarded that in lieu of Element.patch(vDOM):
Element.patch(<div title="new title">new text</div>);
as the later is more humanistic I would say.The trouble for browsers, is if certain DOM apis have a dependency on the layout of another element. My naive and unvalidated understanding:
// Good: These DOM calls in a single frame will trigger layout-paint-composite (1 loop)
- e.style.backgroundColor = "red";
- e.style.width = "20px";
- e.style.transform = "translateX(10px);
// Bad: These DOM calls in a single frame will trigger layout-?-layout-paint-composite (2 loops)
- ...
- e.style.height = otherElement.offsetWidth + 200 + "px"
- ...
The reason being that without knowing the width of "otherElement", there's no way for the js runtime to execute the "e.style.height" line and execution needs to be paused while layout occurs.If you're looking for a transactional syntax (similar to what you've proposed) that also addresses this though, fastdom looks like a good option:
fastdom.mutate(() => { element.style.width = "20px" });
I'm not a browser expert though so if I"m misunderstanding something, would love to know.On the contrary, there is evidence that quite a few people are considering that.
https://developer.apple.com/documentation/uikit/uiview/16226...
Software developers: well yes, 99% of the cycles I use are completely needless, but it's still plenty fast enough!
Which we justify with the idea that a framework like React is abstract, hence expressive and productive.
Excuse me? Abstract? React is absurdly low level.
25 years ago I was coding in Borland products. You visually drag you UI together. Link events with a click. Drag data sources and fields into UI to do two-way binding. Tooling chain? What tooling chain. There's a build and a run button. No weird text config files or CLI crap. And every setup is the same.
25 years later we're worrying about painting frames. We're pissing away impressive hardware performance whilst simultaneously not actually achieving abstraction. That's a double fail.
Any device with a modern web browser can run a React application. Sure, Electron and the alternatives are resource hungry, but they allow developers to create true cross-platform applications.
Sure, there are other ways to create a cross-platform app, but none of those approaches allow you to tap into the massive number of web developers that exist.
I would define Flutter as ”true cross-platform”.
Update: React + ReactNative too.
SPA technology brings some key advantages but also a whole new realm of cost and complexity. It's my experience that SPA popularity has convinced many folks to use it when they really don't have a practical reason to justify it.
And honestly, most of the weight in modern websites comes from analytics and tracking tools. I've made insanely performant SPAs that work well on low budget hardware. My personal phone is 5 years old, if code I write doesn't perform flawlessly on it I'm not going to ship it to customers! Heck my personal dev laptop is a 4+ year old $999 Costco special.
Well made React sites can run just fine on devices like that, and Svelte runs even better.
Also SPAs scale better, I remember the days of website backends being slow, with page loads taking forever, massive amounts of HTML being generated server side using slow scripting languages.
Sending a JSON blob of some fields to the browser and having the browser update some nodes makes a lot more sense than the old school way of reloading the entire bloody page to change a couple fields.
Plus this is not an all or nothing sort of choice. For decades we have used Ajax to perform partial updates on a web page. Consider alternatives like HTMX as a comparison.
If your McCrud app can't be responsive on a baseline 1Ghz PIII with 1GB of RAM, then there needs to be some sort of shame pushback. Moore's law is effectively coming to a close, there will need to be more optimization in the future.
- Svelte is actually strangely slow, I mean there's *one* interesting optimization that having a custom compiler/transform allows you do to for free, which is deep cloning nodes in one go rather than creating them one by one each time, and they ain't doing it. Also, I don't have proof of this anymore, but I had tried running my relatively naive framework without the deep cloning trick, and without any custom transform or compiler at all, on that benchmark, and it was _still_ significantly faster than Svelte. Like Svelte is not that fast when you look at it closely, despite what the perception of the average developer might be, or what the marketing might say.
- Inferno is fast for real in that benchmark, and it isn't using signals, which is very interesting. I don't know how Inferno works in depth, but looking at the Inferno implementation for that benchmark [0] I see some shenanigans. Like what's that "$HasTextChildren" attribute? Why is my event handler created like that? Like I'm doubtful that the result in the benchmark will actually translate exactly to the real world.
- It's interesting also: if the VDOM is pure overhead why is Svelte creating an object for each instance of a component, kinda like React is doing? You don't strictly need to do that, as proof of that Solid doesn't do that (in production builds), because that's pure overhead for real there.
[0]: https://github.com/krausest/js-framework-benchmark/blob/6388...
This does translate into the real world, if developers use the flags. I know their babel plugin uses some heuristics to auto apply some of these things, but its extremely conservative.
The flags themselves are available in the real world though and can be used to achieve high performance.
Its really a shame Inferno never caught on the same way as other frameworks. Its extremely fast and intuitive, and had a nice take on functional components (just add the lifecycle methods as props, instead of introducing what is now React Hooks, though I think Inferno is held back not having a hooks API for some level of mindshare and compat there).
Even SolidJS hasn't quite crept the performance Inferno has managed to achieve.
EDIT: If memory services, the creator of Inferno works (worked?) at Meta (Facebook) as well. For whatever reason, it never garnered mindshare at FB either, despite arguably being a better solution than React in many real world scenarios and coming around at roughly the same time. I have always wondered what the story was there
In reality, most of these benchmarks are not meaningful when talking about real app performance. What's meaningful is how you do global state updates in your app. If you use a react app with react-hook based context providers that unnecessarily update hundreds of components on simple changes, you perf is going to suck. If you use a react app and don't use React.memo anywhere, perf is going to suck. If you use react very carefully and are fully aware of when the vDOM is going to run and use small components that only update when their data actually changes, and ideally avoid running vDOM 60 - 120fps a second for animations, performance is going to be good.
I like Solid.js because it does all this for you by nature of just using the framework. Svelte does some of this for you so for real world apps performance is likely to better than react, but it doesn't do it as well as Solid by nature of it's state management strategy, not by nature of it's DOM update strategy.
The less you update, the faster your app will be. Then the DOM diffing strategy doesn't matter.
This article doesn't really argue against that. They say the VDOM is a "means to an end" and is "generally good enough".
The thrust of the article seems to be that a virtual DOM isn't a guarantee of performance. Rather it's just one solution that can be pretty fast. Svelte happens to take a different approach which is also pretty fast.
More on zero cost abstractions here: https://stackoverflow.com/a/69178445/315168
The concept was popularized by C++ templates. The idea is that the templated code would be just as good as if you hand wrote it without generics. There's no extra function pointers or virtual calls, extra indirection by pointing to some user data, etc.; the tree node or whatever and data struct are declared as a single entity, there's no runtime callbacks, etc.
Sorry for the pedantry, but I believe this would still be wrong as phrased. Maybe "not all abstractions are pure overhead" would work?
In this case, React is slow and memory hungry because when you make a change you go through this process:
1. Update some value
2. … triggering updates to the virtual DOM
3. … requiring it to caluclate the difference between the real DOM and the virtual DOM
4. … and finally apply the changes to the real DOM
That abstraction requires substantial extra state to be stored and managed. If you have a different abstraction which directly manages the DOM, you can avoid steps 2 and 3. The big question is a) does your code do enough manipulation for this to be noticeable? (React is less slow than it used to be but I've seen 4 order of magnitude deltas in optimized React code so it's not uncommon to see it chug with large pages, especially on older hardware like a lot of the public uses) and b) does that other abstraction have drawbacks for your developers which cost you more than the performance savings?
Maybe I should have made it more obvious by referencing the butterflies instead.
That's kind of the ignored superpower of a framework like React, which makes the virtual DOM the authority: there might be a DOM, but there also might not be. Whether the virtual DOM reflects to a real DOM, or native UI, or Qt, or an ASCII terminal interface, it doesn't care.
This is also the part that most web devs have the hardest time with: React (and some other frameworks) are not web frameworks. They really are just UI frameworks, that happen to (also) work in a browser. Even if they were original born out of a web need way back when.
This is the true power of the VDOM, to abstract the view from platforms.
Why be a web developer when you can be a cross-platform app developer?
That's why I don't use Svelte and other web SPA frameworks...
That does, however, require having properly designed your UI, with a knowledge of where the power of your framework of choice is. And that's where a lot of apps fail. Even something as simple yet critical as using vdom keys tends to (for various reasons) never register for many folks, leading to terrible performance.
I'd rather a small perf hit than have another code base for web. You get web for basically free when making a RN app.
That is for APPS you want your WEBPAGE to perform better, don't use a SPA, use Astro or something similar.
Might as well do everything in Assembly or C because it's faster right? Same argument. Development speed matters too.
So it's true that there's still a "typesetting engine from the 80s" in there, but there's also a powerful layer of app functionality built-in as well. It's reasonable to question whether all of this belongs together, but all of this evolution _has_ allowed for whole new classes of applications to be delivered securely and quickly to all sorts of devices in the browser.
To me, most of the issues with web apps stem from the fact that foundation is just not built for that task. Like these weird selection issues, where you move cursor 1 pixel more to the bottom and it selects the whole visible area instead of text - that doesn't even make sense. Or jumping page and blocks all over the page while it loads fonts and stylesheets. Sure, we build more hacks on top of existing hacks to mitigate this, but that's just duct taping with complexity, not the engineering.
Those that do, I don't think they fully understand when and where to `useMemo` and `useCallback` to optimize. It tends to get overused or used in a way that doesn't actually memoize the parts of the component that doesn't change.
Then adding in state management only makes it more complicated in some cases depending on the state paradigm.
It's a mystery that React is as prevalent as it is given how hard it is to actually do well. I think Solid.js and Vue have a much cleaner paradigm as far as re-renders goes (with React being explicit opt-out and Solid and Vue being opt-in).
Having seat belts will not fix car crash deaths.
But it does make them less likely to occur, doesn't it?
X application-level virtual DOM changes -> differ detects only Y < X real DOM changes -> Y final DOM operations
is faster than
X application-level virtual DOM changes -> no virtual DOM diffing -> X DOM operations
this depends a lot of how fast the diffing is and how fast the DOM is but unless DOM operations are instant now (and with CSS, layout reflow, etc. I'm not sure how they could be) then there must remain some situations where VDOM has a perf advantage.
1. Are you updating nodes unnecessarily, especially in ways which force the browser to do more work (see next point)? In general, a tool which does only does the necessary updates is going to win.
2. Are you forcing the browser to do work only to throw it away? The common cause of this in the past was sloppy event handling code where there was a mix of operations updating the DOM interleaved with calls which forced the browser to recalculate the layout (e.g. change the size of an element by changing its contents or formatting, call something which forces the browser to calculate its size, then repeating that cycle so the browser had to repeat the layout calculations it had just made – I remember things like layout code which is now obsolete thanks to CSS flexbox/grids having pathological states where that could happen dozens of times in response to a single update).
That leaves plenty of room for differences from either of the scenarios you listed: for example, a library which doesn't use a virtual DOM at all can avoid all of the overhead related to managing one and diffing it but it has to keep track of its DOM elements to avoid needing to update all of them on any changes. This can be much faster and easy to write for simpler apps but has coordination challenges if your app gets large and especially if it has multiple teams working in the same codebase. The promise of React is that while it's never the fastest it'll be a reasonable balance for not being too slow while scaling up to larger teams.
Compilers were a huge boon over hand-built machine code and assembly. In specific hot spots, someone can eke out better performance sometimes, but compilers emit pretty good performance all the time with much lower effort from the programmer. Early compilers were just okay. Modern compilers can regularly kick 99% of human skills to the curb with aggressive pipelining, speculation, and vector operations.
React is the assembly language in this analogy.
let count = 1;
is demonstrably better than let [ count, setCount ] = useState(0);
Not having to keep useMemo() in mind all the time is demonstrably better when performance can be maintained without having to worry about it.Less code = fewer bugs
Less code with equal or better performance is golden.
Calculating the vdom diff is pure overhead in that if you have better syntax (or a compiler) you can just skip it.
If Svelte way is better at minimizing and batching DOM updates, they should probably argue and show that, not misrepresent what VDOM does (while blaming strawmanning on others no less).
From the end of this article: “Virtual DOM is valuable because it allows you to build apps without thinking about state transitions, with performance that is generally good enough. That means less buggy code, and more time spent on creative tasks instead of tedious ones.“
Okay great, it’s not pure overhead.
This is proven by Solid.js which is faster then every VDOM framework, has an API that is functionally the same as react, and doesn't use VDOM.
With Solid can use JSX and it doesn't even need, last I checked, anything beyond standard JSX transformation to get pretty good results. It's the better direction IMHO even if the Solid APIs feel like they need another iteration or so.
... only to find out that this stuff is actually real and is how a big chunk of the visible web actually works.
You probably haven't done any native UI as native UI uses exactly the same idiom.
CWnd* parent = ...
parent->appendChild( new EditBox() );
native UI also uses DOM concept, it is just that instead of child elements it uses term child windows or [Gtk]widgets or [ns]View s.Hah. https://github.com/Ardour/ardour/tree/master/gtk2_ardour ... c'est moi
Anyway, that's not really the point I was making. Native UI can be thought of and used as a DOM model, but that's not inherent to the process unless you're literally writing traditional database+presentation+edit applications.
I was more poking light-hearted fun at the explosion in terminology and concepts exposed to someone doing web-based "frontend" development, and how little most of this has to do with HTML, CSS and the general classical model of "a browser".
function HelloMessage(props) {
return (
<div className="greeting">
Hello {props.name}
</div>
);
}
And that returns "an object representing how the page should now look"Aren't we developers here. How about an object type. I assume it's a DocumentFragment. Is that correct?
Then it talks in broad (i.e. useless) terms about using this object.
So my next question is: what's exactly is wrong with using a DocumentFragment to just replace that part of the DOM? For example:
let frag = renderMyModel();
if (destElt.firstElementChild) {
destElt.replaceChild (frag, destElt.firstElementChild);
} else {
destElt.appendChild (frag);
}
I do this with a massive DOM tree and rendering is like instant.I think it's easier to think of virtual DOM implementations as doing two things: (1) describing the desired state of the DOM in some sort of structure, and then (2) diffing that structure against the actual DOM in order to make the changes. The key part is that the virtual DOM implementation does the diffing itself in order to make the changes itself.
This is similar to your example, in that you have generated the desired tree and are rendering it in the right place. However, it is different, because the browser will not do a fine-grained diff of all the elements, it's just going to replace them with the new elements that you've given it. This works fairly well if you're trying to replace a large chunk of elements with a new set of elements, but it has problems if you just want to make smaller modifications.
For example, consider some JSX for an arbitrary framework that looks something like this:
function MyInput({ isGreen }) {
return <input class={isGreen && "text-green"} />
}
When `isGreen` changes, we want to update the current value of the DOM with the new version of MyInput, which should just be the same thing with the class changed. The naive (although often simplest and most practical) solution would be to just replace the contents of the element with the return value of MyInput - i.e. replace the input with a new copy, just with the class name changed. The problem comes when a person is typing into the input - if we replace it completely, the use will lose everything they've already written.The VDOM solution is, as I said, to (1) generate, but then (2) to diff. So if the current state is an input with a "text-green" class, and the desired state is an input without this class, it would work recursively: First, is there an element? Second, does the element have the right tag name? Third, for each attribute on the two elements, do they have the same values? Etc. And every time there's a change, it will make the smallest change possible to ensure the current state becomes the same as the desired state. Here, that means removing the class name, but leaving the rest of the input as it is (i.e. with any input from the user left alone).
The third option here is what frameworks like Svelte and SolidJS do: they skip the part of the process where you generate a new desired DOM, and jump straight to the diffing. That is to say, when you create a component, inside that component will be event listeners for each part of the component that needs to change (and only those parts). This listen to changes in the props and state and then change only those parts of the DOM that need to change. That way, you don't need to go through the entire DOM, iterating through all those nodes that stayed the same just to find the class that needs to be updated. Instead, you just directly update the class.
I'll still ask why. Is it just something that React needs to do?
Svelte is being smart and skipping comparisons in the places it knows the result is static. That's nifty. But it also means you have to depend on svelte getting it right every time, in all scenarios.
Long term - I think this is probably the right approach, but it feels very similar to the -03 c++ optimization flag: There was a fairly long period where enabling that flag was considered risky. Each extra transformation carries opportunities for bugs.
It also means extra work at code generation time - it's building a vdom engine specific to your template (again - this is nifty!). Probably not a huge deal, since js build tooling is seeing a LOT of focus on speed, but it's there.
You would have likely not said this if you had ever looked at Svelte-compiled JS. The amount of mutation is surprisingly small and easy to follow. Especially when coming from a world with JSX.
> "it's building a vdom engine specific to your template"
Because that's... exactly what it's doing. It's doing it at compile time, and so yes - the amount of mutation in the output is small, which is not surprising at all because most templates are fairly static.
We can quibble over exactly what a VDOM is, but I don't really know that tracking only a subset of the DOM the app might change (aka: svelte) vs the entire DOM (aka: react) really matters. In both cases you need to map changes to DOM updates, Svelte is just being smarter about it.
Last note - picking the right tool for the job is a quote we repeat often in the industry, realistically though that sort of optimization actually makes it harder to reason about writing code especially in a large org. The great thing about these frameworks is that it's a single way to build and you get server and client rendering. I remember back in the day folks would ask "how do you build a page here" and it would kill me to say "it depends I have 10 questions for you"
Above thread summarizes the issue pretty well I think. Optimizing for DOM updates is nice, but you also want to optimize for bundle size and page load time, and at a certain app size the compiler output is always going to be bigger than just using a virtual DOM.
That said, since it is redundancy, I wonder if Svelte bundles are more gzippable (or at least, could be made so).
I've seen good frameworks for managing this but I agree that developers tend to forget it.
Once a view is created, it can't be processed by JS. It can't be stored in an array or an object. You can't count or individually wrap children. This makes it harder to create flexible API's [1].
The question is: are we willing to give up the expressivity of React for extra performance?
I am leaning towards "no", because I believe React's performance issues mainly come from its memoization-based reactivity model rather than the VDOM. When applying `.useMemo` in the right places I can create perfectly performant apps. However, this requires profiling and is often unintuitive.
[1]: for example https://news.ycombinator.com/item?id=33990947
Seriously though, modelling the FE business logic entirely outside the view library and just wiring it up to observables where necessary is extremely refreshing, maintainable, and FAST.
... it's real hard to get past the simplicity of "Just change the URL property to a new value" that the current DOM API provides. Losing that would suck.
Even open source projects are guilty of this type of grifting, everyone wants to win, even without money in the game.
Virtual DOM is pure overhead (2018) - https://news.ycombinator.com/item?id=27675371 - June 2021 (289 comments)
Virtual DOM is pure overhead (2018) - https://news.ycombinator.com/item?id=19950253 - May 2019 (332 comments)
- Make it work
- Make it fast
Reality is, most of developers just want to get shit done and go home. The bosses of course never want to pay you for "make it fast".
The point of React is of course, low overhead JS class/function to decompose large UI. That made the job done.
I think I'll try next just toggling visibility instead and skipping the removal of children. Sure this is a bit of edge-case but there's no definite silver-bullet here. Don't know about Svelte internals if this is something they could do eg hiding the components before destroying them. But well it'll still jank (but not as visibly) as everything is done in the same UI thread.
Virtual list of course would probably be the optimal solution.
Great to see.
> You can do the same thing without JSX...
Well, the end result is that there is no JSX. It's gone. It's syntactic sugar.
Sorry this is nonsense.