You want to return a description of the DOM, rather than the real DOM, because you want to be able to reevaluate your templates repeatedly with new state, and efficiently update the DOM where that template is rendered to.
All the examples here use imperative DOM APIs to do updates, like with this:
function TodoInput(attrs: { add: (v: string) => void }) {
const input = <input /> as HTMLInputElement;
input.placeholder = 'Add todo item...';
input.onkeydown = (e) => {
if (e.key === 'Enter') {
attrs.add(input.value);
input.value = '';
}
};
return input;
}
class TodoList {
ul = <ul class='todolist' /> as HTMLUListElement;
add(v: string) {
const item = <li>{v}</li> as HTMLLIElement;
item.onclick = () => item.remove();
this.ul.append(item);
}
}
Avoiding those `input.onkeydown = ...` and `this.ul.append(item)` cases, and instead just iterating over items in your template, is probably the main benefit of a VDOM.(The problem with VDOMs is that diffing is slow, a problem solved by using templates that separate static from dynamic parts, like Lit - a library I work on).
In fact I now strongly believe it's counter productive, because most people come to it thinking "I can just trigger however many re-renders of this large piece of UI as I like, the vdom makes it ok" and it doesn't, the performance sucks, but now you have architected the app in a way that requires a rewrite to make the app perform well.
I have seen this exact sequence of events four times, by four different teams. The second, third and fourth, as a principal architect consulting for the team I tried to intervene and advocate for a vanilla architecture that is mindful about performance, citing the issues they would likely experience with react, but to no avail. There was a lot of "oh but there many ways to avoid those issues" followed by a list of things I was presumably ignorant about.
I guess most of us need to learn things the hard way.
Specifically, I'm hoping to show that vanilla architectures can be not only performant, but easy to maintain with well designed code that uses stable and known patterns. Using JSX just so happens to clean up the code nicely and make the relationship between React and vanilla very visible, but that's really all it did here.
Although to be fair, the hack required to get JSX-as-DOM to work is really unfortunately and I'm not very happy with it, and I would prefer JSX to just render as an object tree that anyone can render however they want. But when I tried that for a few months or a year, it was not nearly as performant as rendering them as strings as soon as they're evaluated, which can then be cached via standard module caching. At least, that's how I got immaculatalibrary to entirely render all HTML files in ~700ms initially and ~70ms on most file changes.
I'll try to do some experimentation next week to see if I can get more performance back out of having <foo bar={qux}>child</foo> to render as {foo:{bar:qux, children:[child]}} again though, because that would absolutely be the ideal, and would unfork JSX in the same way Typed Annotations proposes to unfork JavaScript types.
When people say that React is fast, what they mean is that React can dom-diff faster than a naive O(n) approach. It means that updating a component with a thousand nested divs won't crash out your browser, like it might if you were to write the code by hand. It doesn't mean it's an objectively high-performing framework.
What React is good at is forcing you to write code in a clear, comprehensible way. Having every engineer on your team obey F(props) = state is a strict improvement over virtually any other paradigm. (Yes, you can still make a tangle of components if you try hard enough, but the complexity of the tangle is capped significantly lower than the complexity of a tangle of JS without any framework attached.)
Separate source of truth from derived data. Separate possibly valid user intent from validated state. Use contexts to organize the data dependency graph of your application. Ensure all your widgets use a consistent value type in and out, don't let events contaminate it. Use something like cursors or optics to simplify mutations and derive setters automatically.
I've never had an easier time building very complex UI functionality than with React. But it requires you to actively start reasoning about change in your code (what doesn't change), and this is something most people are not used to.
Personally I think React compiler is folly for this reason: they are taking the most interesting part of React, the part that lets you write apps that are incremental from top to bottom, and telling you it's too complicated for you to think about. Nonsense.
The issue is just that React makes pros feel like idiots unless they eat some humble pie and grok the principles and the reasons behind it. Which is that React is what you get when you try to come up with a UI architecture that can make entire classes of problems go away.
Without a VDOM, one way data flow, and diffing, your UI won't just be slow, it'll be full of secret O(n^2) cascades, random ifs to stop infinite cycles, random "let's update this state early so other code can immediately use it" ordering issues, and so on.
A rewrite on those occasions, I do not consider possible. Most businesses are way to deep into frameworks.
What I personally advocate is to choose frameworks with valid escape hatches, and then make sure to staff a team with JavaScript programmers rather than some-framework Andies.
In an even larger scale, I advocate for web-component based solutions. I try to stay close to vanilla as much as I can. Whether I like it or not, it's the highest value professional decision, imo.
As far as I understand, signals make it much easier to keep the DOM updates to a minimum.
More concisely: JSX is just an alternate function call syntax with some useful applications.
For example at my last company we used JSX to make test data factories that had an XML-like look but were using a builder-pattern in the element creation that was able to make contextual decisions about what was in the final data. Nothing to do with React, DOM, or inability to express the same thing declaratively without JSX.
Thats really interesting, can you elaborate more?
For example did you use a specific JSX compiler? Was that written in house or used a 3rd party library?
Depends who "you" are. I prefer to have my DOM nodes updated in place without all the reconciliation machinery. (no implication about what you want)
If all JSX does is return a DocumentFragment that you then need to imperatively add event listeners to and imperatively update, how is it much better than innerHTML?
I get the "no more imperative updates" dream. I've used these frameworks for probably a decade. I've mastered them.
Me personally, I prefer imperatively updating my DOM. I get completely fine-grained control over what's happening. I can architect it to be an extremely efficient machine. I can make it extremely easy to add/change/remove/fix features in my apps without forcing myself to think according to anyone else's opinionated methodology.
The farther you get into complex GUI territory, the more you want a declarative, functional approach, because it makes things simpler. The closer you are to a handful of controls with simple logic, the more you want to just imperatively tell them what to do, and leave the rest of the page alone, because it makes things simpler. We now just have better tools than jQuery for that.
I feel like this is one of the leakiest abstractions in all of computing. There's a reason there's an entire cottage industry around react; how to stop things rendering multiple times, refreshing needlessly, etc.
[0] https://blog.metaobject.com/2018/12/uis-are-not-pure-functio...
>you want to be able to reevaluate your templates repeatedly with new state
No you don't. It is inefficient and increases complexity. You then have to extract and keep track of state yourself where the platform/UI components could have done much of this themselves.
Calling the same method over and over again is wasteful.
So we don't do that.
First, we did not start with the obviously incorrect premise that the UI is a simple "pure" function of the model. Except for games, UIs are actually very stable, more stable than the model. You have chrome, viewers, tools etc. What is a (somewhat) pure mapping from the model is the data that is displayed in the UI, but not the entire UI.
So if we don't make the incorrect assumption that UIs are unstable (pure functions of model), then we don't have to expend additional and fragile effort to re-create that necessary stability.*List reconciliation still has to happen, but you don't need to pull out an entire vdom. You just have to have some mapping between list items and their resulting DOM nodes.
This mind numbing reliance upon layers of abstraction nonsense around state management is why I really don't like React. State management is ridiculously simple. State management, when done correctly, is the most primitive example of MVC with no abstractions needed.
You might be able to handle this while you're alone and you know everything about your codebase, but the moment you're working with someone else or in a team this will no longer be the case.
It's also not even an either-or. I've worked on a codebase that did both: React was loaded for some views & others were served with a lightweight JSX renderer.
I wanted to add my two pennies to the discussion. You are of correct that with that approach you lose the declarativeness but sometimes you don't need that, if the thing is mostly static.
I went this road many years ago for a project. The nice thing of this approach is getting rid of all the ugly DOM API but enjoying it's performance over innerHTML.
(1) I absolutely love lit-html but don't like the rest of the lit components framework. Luckily you can use it independently!
It works great! I was amazed at how you can do so much in templates, it's pretty much everything I could do in Vue templates, though a little more verbose.
I built my own `VanillaComponent` class, which has a mount() method which calls the render() function which I define on the child class.
My VanillaComponent class looks like this:
import { html, render } from "lit-html";
abstract class VanillaComponent {
abstract render(): ReturnType<typeof html>;
private _mountPoint?: HTMLElement;
mount(this: VanillaComponent, target: HTMLElement) {
target.textContent = '';
this._mountPoint = target;
this._update();
}
_update() {
let templateResult = this.render();
let rootPart = render(templateResult, this._mountPoint!);
}
}
So I can write something like class MyComponent extends VanillaComponent {
constructor(props: { label: string }) {
this._props = props;
}
render() {
return html`<button>${this._props.foo}</button>`;
}
}
Then I can instance like so: let myComponent = new MyComponent({ label: "I am a button" });
let target = document.querySelector("#demo");
myComponent.mount(target);
The base class stores the root node (target), so later I can do myComponent.update()
to re-render, taking advantage of lit-html's "diffing" logic.However something I have not been able to solve with lit-html only, is when I compose parent and child components I have to do something like :
class MyDialog extends VanillaComponent {
render() {
let childComponent ... // another VanillaComponent previously instanced
return html`
<div>
${childComponent.render()}
</div>
So the child component I need to explicitly call render() to get the TemplateResult for the parent template.But this means I can not do `childComponent.update()` because I don't know the root element of child component, since I did not mount it explicitly myself.
I mean technically because of the lit-html optimizations, I can do `.update()` on myDialog (the parent component) after any child component's props changes, and it will only re-render what is necessary... but let's say my child component has like 1000 cards... it seems very wasteful and it would be ideal if I could re-render only the child.
I wonder if there is a trick to get around that with just lit-html?
I think in this case the ref() directive - i.e. https://lit.dev/docs/templates/directives/#ref - may be what you want. If it isn't exactly, reading how it's implemented would be my first step towards building something similar that is.
[edit] Oh also, this solution works really well for SEO. That's another problem I didn't find solved well in other JSX frameworks.
That said, JSX can be used easily with Astro as long as you get used to at least a bit of The `.astro` syntax for wrapper components and pages/layouts.
Another user below said
> We've recently moved one service from next to Astro and it was just removing a ton of boilerplate and 'dance around' code.
And I get why it happens. When you first try out a new framework, you allow yourself to learn and add its inherent complexity, knowingly and intentionally. You say to yourself, "it's part of the dream, it's going to work out; there's a vision, just trust the process." This is true with literally all frameworks.
But they never deliver. The complexity is never worth it, and in the end, the intentionally added complexity is always intentionally and gladly removed when it becomes clear that it was unnecessary complexity. This is what I am glad to have learned so thoroughly that I no longer try to learn new frameworks when I initially see its complexity, imagine adopting it in view of my experience, and recognize that its almost always not worth it.
Look at the code on vanillajsx.com. Besides JSX and types, it's plain JavaScript and DOM manipulation. Translating it to document.createElement would add almost no lines of code. There's no unnecessary complexity. That's the whole point of the site. The simplicity of discovering and removing unnecessary complexity is wonderful and refreshing, and I think a lot of people agree.
Coming from Vue I was really surprised it does a lot of what Vue templating does, including attaching events, with just vanilla JS templates. And when you use VSCode lit extension, you get syntax highlighting and full type checking inside the templates.
I learned about lit-html after a tweet from Marc Grabanski, where he said he used lit-html with vanillajs, not Lit.
After some experimenting I found it works great and it seems like you are trying to solve something very similar.
When you use the lit-html template package you can do basically evetything that is described in the Templates chapter
https://lit.dev/docs/templates/overview/
... without all the other abstraction of components that are part of lit-element.
https://lit.dev/docs/libraries/standalone-templates/#renderi...
Var button = html(’<button>im a button</button>’);
The html() function is trivial, but it just doesn’t feel like real programming to do this, even though there’s nothing else to it in the end.
[1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...
But maybe we can revive the general idea with a modern take: https://github.com/WICG/webcomponents/issues/1069
Isn't this what we have in TFA?
But even considering the single implementation problem, it also was just not a good language model, nor was it well specified or defined and it brought with it a pile of weird baggage and complexity.
Then because it was The Future there was no real thought into proper interop with JS (it was essentially a completely independent spec so adopted general syntax but specified in a way that meant JS could not simply adopt that syntax).
Firefox never shipped the optional E4X DOM APIs. I wrote a polyfill for them at the time.[1]
VanillaJSX seems to suffer from the same problem though.
https://github.com/tomtheisen/mutraction
It lets you do stuff like this.
const model = track({ clicks: 0});
const app = (
<button onclick={() => ++model.clicks }>
{ model.clicks } clicks
</button>
);
document.body.append(app);Another interesting thing is that other JSX libraries like Solid.JS also return DOM nodes, and I love that this idea is gaining traction
The closer we get to the platform we're using, the better. Being removed by layers of abstractions CAN be useful, but in practice, I haven't found a use for abstracting away the platform. (yet.)
Maybe huge projects like Facebook benefit from this tho (which I haven't worked on)
Technically this is the only code bundled with vanilla jsx:
Honestly, the real interesting part about my framework is literally everything else. Returning strings from JSX on the ssg-side; being able to import raw source directories and manipulate string|Buffer at ssg-time; the extremely efficient and lightning fast module system I wrote on top of chokidar and swc; probably more I'm forgetting, but basically the JSX-as-DOM is only the most visually interesting part. But really just a party trick.
[edit] Case in point: the source code to vanillajsx.com is extremely concise and clear and short, I literally wrote the whole thing today with zero deps (besides imlib), and the JSX-as-DOM demos are the least innovative part of it: https://github.com/sdegutis/vanillajsx.com/tree/main/site
For those that appreciate this approach of JSX returning concrete DOM elements, Solid works exactly like this, with the addition of a proper reactivity layer.
* https://www.immaculatalibrary.com/books.html (src = https://github.com/sdegutis/immaculatalibrary.com/blob/main/...)
* https://www.immaculatalibrary.com/prayers/ (src = https://github.com/sdegutis/immaculatalibrary.com/blob/main/...)
Your best bet would be to queue up specific UI changes that need to be made as diff's rather than checking the entire UI state. At that point, though, you might as well run them immediately as the change is needed.
If that was still a perf problem you would end up chasing a very complex solution like react fiber to partially update the UI on a loop while periodically pausing for user events.
Here’s an app written using Vanilla TSX: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
Also, it sounds like the only browser to ever support it was Firefox? That was probably much more of a limiting factor for adoption.
However, the XML selector syntax was a godsend. Recursively parsing an XML tree is really a pain. E4X would allow you to do things like:
var foo = someXml..childNodes.@attribute;
I'm not even sure if that would work actually. There were a bunch of operators for doing things like getting a collection of children that all had the same tag so you could work with XML like: <someXml>
<intermediateNodeYouWantToSkip>
<childNode attribute="1" />
<childNode attribute="2" />
<childNode attribute="3" />
<unrelatedNode />
</intermediateNodeYouWantToSkip>
</someXml>
Another post here said people didn't want it, but I don't think that was the real reason it was dropped. There was a lot of drama at the time about Flash in general and a massive debacle about EcmaScript 4 (which ActionScript more or less adopted). There was also the whole XHTML thing happening.Basically JSON as a format won out over XML and ES4/XHTML were ditched. Frankly, a world that revolved around XML/SOAP would have been a nightmare, so I guess killing off the easy processing of XML in JavaScript helped to stave off that potential future. XSS, XSLT and E4X were all casualties.
Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function h("div", {}, [
h("p", {}, "this is easy"),
...list.map((l) => h("li", {}, l),
])
With this you automatically get loops, variable interpolation etc without having to invent a compiler and new syntax. Can someone help me understand? <div>
<p>this is easy</p>
{list.map((l) => <li>{l}</li>)}
</div>
> you automatically get loops, variable interpolation etc without having to invent a compiler and new syntaxTo be fair to JSX, you use regular loops, interpolation, etc without any different syntax (`{}` accepts a vanilla JS expression), you just obviously need the compiler step to de-sugar the element tags to `createElement` calls.
And that is what a lot of the pushback against React seems to come down to as well -- it is overkill for simple things. And it is. But different people doing different apps with different levels of complexity... all have different needs.
So it is not as simple as one having appeal over the other. They are different tools for different problems.
But to your point, JSX doesn’t really do much. Your h function is basically what React.creatElement does. Google “React without JSX” and you’ll see how it looks.
JSX is just syntactic sugar over React.creatElement. And that is what makes it so nice… there _are_ no special constructs for loops, or variables, or components. They are actual JavaScript loops, JavaScript variables, and JavaScript function.
It makes JSX easier to reason about than most templating languages.
No better article than this: https://blog.vjeux.com/2013/javascript/react-performance.htm...
https://svelte.dev/blog/virtual-dom-is-pure-overhead
React is also at the point where re-rendering the whole app is a fiction the library maintains for you while being smarter and doing less, why not go the whole way?
I'm asking because, many of React (or friends) introductory material naturally focus on building things like TODO lists or Tic Tac Toe; while those offer insights into how to work with React (& cie), they're not showcasing cases where the performance gains are perceptible, and IMO not even cases where the "organizational" benefits of such libraries are salient.
While there's no doubt that React and virtual DOM offer advantages, it's essential to clearly demonstrate where and how these benefits manifest in real-world applications.
> they're not showcasing cases where the performance gains are perceptible
According to this commenter, it's not even about the performance gains:
https://news.ycombinator.com/item?id=41271272
> and IMO not even cases where the "organizational" benefits of such libraries are salient
Apparently, that is what it ultimately boils down to:
If you assume programmers who don't know what they are doing it is a very different question. Some people will manage to make a train wreck both with and without a framework. But if we assume that there is a skill level where people will manage to make something useful with a framework, but not without it, or vice versa, then I really do not know which way it swings.
One project I worked on was a BI tool with a fair amount of complex state. Before we introduced react we were using backbone, event listeners, and managing dom state ourselves. With react we made it declarative, which simplified things.
Another project was an email client with a lot of complex state and caching. That started with and continued to use react so I don’t have a direct performance comparison. But again, managing the state manual would have been a nightmare.
You still end up with the bloated bundle size but with one of the more modern react alternatives you can eliminate that too. So at least for me, I don’t mind the build complexity for the power I get; especially now that node itself is supporting typescript, the build side is getting simpler to set up as well.
Was super productive and easy to create a Cloudflare Worker Web App that’s free to host thanks to Cloudflare’s generous 100k daily worker request limit.
Generally don’t believe in serverless for larger Apps, but for small websites that you just want to create, deploy and ignore - it’s great!
el = <button>Click me</button> as HTMLButtonElement;
What would be the downside of el = html.button('<button>Click me</button>');
?That way no compilation step would be needed and debugging would be easier as the code executed in the browser is the same code the developer writes.
I don't understand why people take such offense to calling document.createElement() or document.getElementById() or kind of document. or window. function. It's consistent and native.
class Item extends EventTarget {
#checkbox = <input type='checkbox' /> as HTMLInputElement;
li;
constructor(private list: List, text: string) {
super();
this.li = (
<li class='item'>
{this.#checkbox}
<span onclick={() => this.toggle()}>{text}</span>
<button class='close' onclick={() => this.remove()}></button>
</li> as HTMLLIElement
);
this.#checkbox.onclick = () => this.toggle();
}
}
with class Item extends EventTarget {
#checkbox;
li;
constructor(private list: List, text: string) {
this.#checkbox = createElement('input');
this.#checkbox.setAttribute('type', 'checkbox');
this.li = document.createElement('li');
this.li.className = 'item';
this.li.appendChild(this.#checkbox);
const span = document.createElement('span');
span.onclick = (() => this.toggle());
span.textContent = text;
this.li.appendChild(span);
const button = document.createElement('button');
button.className = 'close';
button.onclick = (() => this.remove());
this.li.appendChild(button);
}
}
Of course you can create helper functions to avoid all the `createElement`s followed by `setAttribute`s. As mentioned elsewhere you can even used tagged strings.
But doing things "manually" is painful.With the second of you have stringa.
After “How would they handle large data?” it turns into an unreadable mess.
Communication between elements is not covered, global deps, DOM updates scheduling, content projection, and so on - you “just don't need it” in small demo examples, but you do need it in the real apps.
Clojure datastructures makes this so much more enjoyable. Everything is just basic lists and maps which makes it very flexible and powerful.
[:ul [:li "task 1"] [:li "task 2"]]
It's weird that it's not more common for making web apps.
h("ul", h("li", "task 1"), h("li", "task 2"))
This is called "hyperscript-style" after an early library that used it. This is basically what JSX compiles to too. There used to be a lot of JSX vs hyperscript debates.There's also variants like h.ul(h.li("task 1"), h.li("task 2")) using Proxies now too.
Trouble is if you're used to HTML it can take a while to get used to it. It's like a learned helplessness or something.
Braces and semicolons exist so you can do that.
Sometimes it's just easier to read an array that's split over multiple lines.
Sometimes it's just easier to read a statement that's split over multiple lines.
Sometimes it's just easier to read two statements sharing the same line.
If your language's restrictions is making code harder to read, then it's making your job harder than it needs to be.
Not what you'd expect to see.
So I would like to explore the ability to use JSX in non-DOM environments. react-three-fiber does this with Threejs, but then it is still React oriented. I found this article about parsing JSX https://blog.bitsrc.io/demystifying-jsx-building-your-own-js.... And I know babel has something that parses JSX.
Does anyone have recommendations for doing this. Threejs to me a good candidate - a non React version, since it is a hierarchical system (scene, meshes, materials etc), but I suspect there are other applications.
I made an attempt to implement a Javascript version of Hickey's transducers - a sort of conveyor belt of functions and that is another instance of a series of processing steps that might be best represented in JSX
JSX doesn’t actually write anything, it’s just a templating language over React.createElement.
It’s the virtual DOM that actually syncs those structures created by createElement to the real DOM. So it’s the virtual DOM that allows you to write your code declaratively.
That’s evidenced by OP’s project, which is JSX without the declarative piece. You just get an Element and then you have to update it imperatively if you want to change anything.
<html lang="en">
<body>
<h1>Web Components Examples</h1>
<h2>Counter Component</h2>
<counter-component></counter-component>
<h2>Clickable Button Component</h2>
<clickable-button></clickable-button>
<h2>Toggler Component</h2>
<toggler-component></toggler-component>
<script>
class CounterComponent extends HTMLElement {
constructor() {
super();
this.count = 0;
this.button = document.createElement('button');
this.button.textContent = this.count;
this.button.addEventListener('click', () => {
this.count++;
this.button.textContent = this.count;
});
this.attachShadow({ mode: 'open' }).appendChild(this.button);
}
}
class ClickableButton extends HTMLElement {
constructor() {
super();
this.clicked = false;
this.button = document.createElement('button');
this.button.textContent = "Click me!";
this.button.addEventListener('click', () => {
this.clicked = !this.clicked;
this.button.textContent = this.clicked ? "Clicked!" : "Click me!";
});
this.attachShadow({ mode: 'open' }).appendChild(this.button);
}
}
class TogglerComponent extends HTMLElement {
constructor() {
super();
this.on = false;
this.button = document.createElement('button');
this.button.textContent = "OFF";
this.button.addEventListener('click', () => {
this.on = !this.on;
this.button.textContent = this.on ? "ON" : "OFF";
});
this.attachShadow({ mode: 'open' }).appendChild(this.button);
}
}
customElements.define('counter-component', CounterComponent);
customElements.define('clickable-button', ClickableButton);
customElements.define('toggler-component', TogglerComponent);
</script>
</body>
</html>Mostly I just do Vanilla.js, but the vanilla DOM creation functions turn really verbose, I got tired of that and created this to cut back on code size and increase readability.
There are other libraries that do something similar, but in my own very biased opinion this is one of the better.
It looks simpler at first glance/ to a untrained eye; but it's just adding complexity without really solving any problems.
I like approaches like Kotlinx.html, scalatags, Elm's HTML package or HtmlFlow. They are also abstractions, but they add typesafety that html-as-a-string does not offer. On top of that you get breakpoints, code completion, and you can keep working in one language.
If you can control your app’s structure, it primarily adds significant increases in the RAM and CPU required for your app and slows load time because you are using a huge amount of JavaScript to emulate the carefully tuned C++ code built in to the browser. If you notice, most of the benchmarks from when React launched claiming performance wins were compared to heavyweight frameworks or complex jQuery plug-in combinations where a single user interaction might trigger cascading updates forcing the browser to rerender things which didn’t change along or to reflow multiple times in cascading update-measure-update chains. Pure DOM implementations were always faster, often by multiple orders of magnitude and once you could drop IE6, and then IE11, the DOM APIs and CSS were rich enough that much of the library code is now a net negative as well (e.g. people used to use complex code trying to build layouts which CSS grids solved).
It can also simplify some stuff surrounding event handling (but that's not it's main goal I think)
So people wrote various ways to defer/batch/denounce updates.
Virtual DOM is a general solution/implementation. It's not the only one, but I think you always need at least a tiny runtime to avoid too much DOM access (ie Svelte, Solid JS are fairly minimal)
Unless you use lit-html, which has a very efficient diffing algorithm that only updates the nodes that have changed.
People often mistakingly describe the vdom as faster than the dom, this is incorrect. It would be faster than throwing away the whole components dom and rebuilding, so the same templating code building a new dom, rather than a vdom that's then diffed. Hand crafter mutations will be faster than a vdom diff, simply because the computer is doing les work, however much more error prone.
Garbage collection is less efficient, but it is sometimes very difficult to figure out exactly when a piece of memory stops being used, which leads to use-after-free, double-free and memory leak bugs.
Same goes for classical UI approaches. In classical UI, most pieces of state are kept in at least two places, once in code and at least once in the DOM.
For example, in a shopping cart, the total might appear three times, implicitly in the code (as a function that sums the prices of all the items), once as the label of the "open cart" button in the navbar, and once as text in the "your cart" modal, which that button shows or hides. THe cart may be modifiable from different places, the cart modal itself, product pages, product collection pages, order history (when re-ordering recently purchased items) etc.
In the classical approach, you need to make sure that all modifications to the cart accurately change the state in all three places. You also need to ensure that if you remove a product from the cart using the modal and you're currently on a page that lets you order the product in any way, the "remove from cart" button on that page needs to turn back into "add to cart", and there may be hundreds of different such buttons, which the cart modal needs to handle somehow. It is very easy to make mistakes here and have the state in the code (array of products) fall out of sync with what the user sees on the page.
In React, there's just one array of products, one function to calculate the total, and a lot of places in the code that use this array. WHenever the array changes, the pieces of the page that rely on it automatically re-render, while everything else stays the same. There's no way for the UI and the array to fall out of sync, and there's no need to track where the array is being used and where it's being modified.
You'll get better performance with _carefully crafted_ DOM access, but that's easier said than done, especially on a larger applications.
vDOM takes care of the "carefully crafted" part with some trade offs, especially if it also defers rendering and doesn't wccess the DOM on every update.
So yes, it's easier to write declarative UIs with it, but it's also there to address common performance issues with unchecked/eager DOM access. Even if you don't throw away the whole tree and insert a new one, it can be very slow. Just _reading_ from the DOM is slow _and_ everything stops while that's being done too.
ultimately go to react.dev because: "Maturing is realizing React is best"
It started as a static site generator but added a bunch of support for client JavaScript too.
It also reminds me of that Douglas Adams line about flying: it's the trick of falling and completely missing the ground, so in order to do it, you can't think about it too hard.
I would guess there is more overhead in creating a dom element than a JS object (which JSX elements compile to).
First, you have to understand that at that time Firefox was about 500x faster at accessing the DOM than Chrome and about 250,000x faster accessing the DOM via the API methods than via querySelectors. Firefox and Chrome performed about equally in use of querySelectors with Chrome being a tiny bit faster. So, the DOM was already fast, but occupied a different memory space than JS.
At any rate the original motivation had nothing to do with performance. The goal was to introduce a template system that fit with React’s state/component system. JS modules weren’t a thing yet, so code organization was very different at that time and centered around concepts like AMD and Common.js, though it was mostly some form of AMD typically require.js.
The design of the template system in Vue was created to solve for the exact same conditions according to the internal organization of Vue.
- JS object appears to be at least 2x faster than document.createElement() (Chrome)
- Note: JS object only loosely represents JSX element so it is a bit unfair. But with actual JSX objects I would assume it is still somewhat faster than the DOM API.
https://youtu.be/DgVS-zXgMTk&t=1532
- Pete Hunt, one of the React devs, says "JSX is faster than the DOM because it is JS memory."