React's UI State Model vs. Vanilla JavaScript
arihantverma.com
arihantverma.com
Just put the checkbox html & css on the page. Write a function that updates the label based on checked state. Call it on input/change events.
It's like 3 lines of js.
React.js people always invent this vanilla js / jQuery strawman that's so complicated and spaghetti that no mortal can possibly understand it.
It's total BS. You can write succinct maintainable code without a 100 lb framework.
The vanilla code was much easier to maintain too. React comes in your way when things get complicated and the work arounds produce really messy code.
Edit:
Project (GitHub): https://github.com/labmlai/labml/tree/master/app
It's a mobile/web app to monitor machine learning experiment.
It's not the final say. Just a strong signal among many others.
Also there is nobody stopping you from writing extremely messy React applications...
Also I think you got a typo at https://github.com/labmlai/labml/blob/master/app/ui/src/view...
Ton of abstractions is an overstatement. https://github.com/vpj/weya is more like a helper function around `document.createElement`.
This all seems so narrow and parochial, like people who have never left their own village. There's a wide world of useful technologies and design techniques out there (including React) and the simplest possible design that solves your problems is usually best.
People running away from "bloated frameworks" very often end up reimplementing a significant portion of the bloat. But usually with fewer ressources and fewer testing, so you can guess what happens most of the time...
Project Manager: Now add 3 checkboxes, one being 'N/A' and when you select N/A the other checkboxes are disabled and unchecked. And update the labels, and have additional form sections showing depending on which checkbox is displaying.
Probably not that difficult, and probably a bad example, but with React/Vue/etc. it's a lot easier to deal with complex states than manually manipulating the DOM.
From experience trying to be clever and anti-framework ends up just creating another framework that's worse in every imaginable way.
Reactive frameworks do make your life simpler (at least once you pay for the large one time cost of using them), but the change isn't enormous.
All you need to do is call setState
Virtual DOM qualifies as clever in my book. :)
Better question to ask is why so many libraries/frameworks proliferate in webdev and not as much in native apps. JS is a suitably powerful language these days.
Also, the attitude of “just use jQuery like everyone else” (paraphrasing the last paragraph and substituting for React) would have precluded the development of React.
With native apps you definitely have frameworks available now. React Native for an example. But the languages and toolkits available basically serve as a framework anyway.
SICP should be required reading for any serious dev if only to expand their imagination beyond an industry that is intensely focused on boiling the difficult and creative endeavor we call software development into a discrete, repeatable process.
How to change this page to this other page as fast as possible, if you don’t know or don’t care to know that other page.
That second qualifier is really important. As long as you have 2-10 screens and know the transitions by heart, nothing stopping you from hand rolling js. But once you start adding states things get tricky really fast, as transitions grow exponentially.
I’ve built some very complicated apps with vanilla js back in the day, and we had ways of dealing with things like that. We called it “reloading the page” where you start from 0 with your state. Kinda like restarting windows to fix it, rather than figuring out whats wrong.
And you could get quite far that way. 37signal’s basecamp was like that - an html app with vanilla js sprinkled throughout. Worked great.
But there is a limit in complexity. JS and html are great for building websites, but if you want to build an actual application, you need to be really clever and accept a lot of limitations. React just lifts the ceiling of what you can do, without being all to complicated.
And you can use the technics of react without react itself too, once you understand what it is all about - https://github.com/ivank/vanilla-teuxdeux
One day React could change from using Virtual Dom to the Svelte way and most user of library wouldn't notice.
The point of the article is that React remember where to make the modification for you.
If you say why does react need the whole state, it’s because React is a translation layer between an immediate mode representation and a retained mode system (the DOM). To illustrate this in a basic way, think of a checkbox. It is an entity in the DOM, but it is also retaining state itself (there is a Checked property that persists as long as it is set, so you know it has state). The only way to know if that checkbox is checked or not is to query the DOM to get the checkbox entity, or to “control” the rendering of the component so you can always derive the checked state from somewhere in your code, and force its state to match. React controlled components are a way to express unidirectional state from your code to the UI.
What situation were you in where vdom was the problem? I’d love to see an example. It is likely that vdom was not the performance issue, something was wrong with the implementation and causing a full re-render. Could you link a sandbox?
But what react does is: for any change in state it creates the entire virtual DOM" regardless of if there was a single checkbox change and everything else remained the same. Then it compares this whole tree* to the real DOM, and then only replaces the parts that changed. The "win" of react is that instead of rendering the entire real DOM again and again it just renders something that is not seen and then swaps out the changed parts in the real DOM.
Why not short-circuit from changes in state to real DOM instead?
I think any google sheet like app would do: you type into some box, this changes state which triggers the creation of the virtual DOM for the entire sheet. Then it compares this entire sheet to the real DOM, finds that only one cell has changed and swaps that out. There will be one entire virtual DOM per character typed. Which is why I'd presume most if not all such applications need to add custom code (using `shouldComponentUpdate`) to fix this scaling issue.
On the one hand it is rare that you'd need to update a thousand elements at once one by one.
On the other hand, and more importantly, the problem that Virtual DOM aims to solve is not that one. Instead, what it wants to avoid is not "updating a thousand elements" but "replacing large sub-trees in the DOM unnecessarily".
That is, Virtual DOM competes, mostly, against doing something like...
someElement.innerHTML = '...<a large piece of HTML>...'
The main concern of doing this is not so much that the performance is bad or not, but that it is intrinsically wasteful. That is, it [almost always] unnecessarily forces the browser to rebuild a part of the DOM and re-render a part of the page... just to change but a few values.This is written in that way because it's easy to write it like that. But, being such a wasteful approach, it does end causing performance concerns.
Going from this, you can use various approaches. A Virtual DOM basically let's you manage your [virtual] DOM fragments as you were already doing but then inserts itself as a middleman to avoid doing those unnecessary DOM replacements and only modify what needs to be modified.
There are two things you need to notice here: First it does add some additional processing an calculation -"the diff"-. Second it still, because there is no other way to do it, uses the same DOM API calls you would use if "manually updating things".
So now you can balance this to see if it's worth it: On the one hand there's a cost -the added calculations-, on the other hand there is a gain -modifying only smaller parts of the DOM-. But be aware that this gain is compared not against "updating many elements" but against "rebuilding parts of the DOM when you don't need to". The distinction is rather important because if, suppose, you actually need to update the content of say a thousand <td>s in a table, your virtual DOM library will still need to do exactly that, it will do so by using the DOM API -because there's no other way-, and any additional processing it does will be added on top of doing that.
So... bad option: build a whole...
<table><tr><td>A</td><td>B</td></tr><tr><td>C</td><td>D</td></tr>...</table>
...every time and dump it all into the DOM element; let the browser do all the work and live with the bad performance you get from having easy to write but wasteful code.Virtual DOM solution: Still write easy to write and wasteful code but apply something in between that reads your "whole block" and finds what actually needs to be done and then does only that.
But it's not the only solution. Another solution is to just don't write that wasteful code in the first place. Instead of building large but mostly unchanged blocks and dump them, only modify what needs to be modified. Of course, it usually means that your code has to keep track of an additional bunch of things -i.e. where in the DOM does each piece of information go-, but this is where those other alternatives mentioned, like Svelte or others, may come into play.
The conclusion from all this is that:
a. No, a virtual DOM is not inherently faster than doing updates manually
b. A virtual DOM provides some gain by not doing unneeded updates of DOM branches. If you were doing those, using a virtual DOM may speed things up.
c. A virtual DOM still needs to do the DOM modifications with exactly the same API you would/could use manually so there's no gain there.
Is it ? I come from the desktop app world and that would frankly barely register above "toy project" ; my experience is more around a few tens / hundred of thousands objects that can change at once (with of course the UI framework only redrawing what needs redrawing).
> There are two things you need to notice here: First it does add some additional processing an calculation -"the diff"-. Second it still, because there is no other way to do it, uses the same DOM API calls you would use if "manually updating things".
I have a hard time understanding how, say, calling 500 methods per second on a DOM object, e.g. `label.innerText = someSensorValueComingFromWebsocketsVeryFast;`, which needs to trigger various events, callbacks, etc... every time could possibly be faster than modifying a pure JS object at the "incoming message rate" and then blitting that object at the screen refresh rate or something similar ?
Updating a value on the DOM 500 times per second just because you can read it 500 times per second falls, again, in the category of writing wasteful code just because it's simpler. i.e. "don't care about performance, just let the browser work".
If you compare code which is initially bad, then sure, a lot of "solutions" will be better than that.
The appropriate comparison for evaluating the value of using a virtual DOM should not be about that, but only about the part you describe as "blitting the object to the screen". You have the data, no matter how, where, or even -to some extent- how often, and you want to put it on the screen.
But the solution to the problem which is
how to go from
inputs (which you don't have any control over)
to
"You have the data, no matter how, where, or even -to some extent- how often, and you want to put it on the screen"
will pretty much look like a vdom, no ?Not necessarily.
Maybe you can find some rendering comparisons from a few years ago. There was some "demo" called DBMonster or DBMON that showed the performance of updating a large amount of values on a table in a web page. The thing is a lot of different implementations were then written by many people using many different frameworks -including many using a virtual DOM and many not using one- and also even some "vanilla" implementations. I don't suggest it for the performance comparison -which you can of course look into- but, more in line with the question: it may be a good way to find a number of different approaches.
That's a mere implementation detail. In practice, letting the browser re-render a single page chunk using its internal implementation will almost always be faster than using JS code to serially update any number of DOM elements. VDOM diffing is a kludge to make DOM manipulation in pure JS usable, it's not actually going to be faster than relying on the browser's own rendering. The biggest problem with the innerHTML is going to be generating the actual HTML, but one can most likely use WASM to speed that up.
Svelte is different and doesn't export a render function -- the component knows what DOM nodes need to change for a given prop/reactive var change, so it doesn't need a virtual DOM (and can be much faster as a result)
A better solution is to emulate the react strategy. Create a state object, on user input you can rewrite sections DOM based on the state. You probably don't need React's diffing algorithm until you run into performance problems. Then you have to limit which parts of the Dom you update to keep it snappy. Then you might as well just use react.
Casually dismissing others by strawmanning them with fake quotes is against the rules. Even outside of that, it's just generally the kind of thing that is neither respectful or respectable. Please don't do it.
Did you actually think I was trying to attribute that quote to op?
We can do better from here, mostly by hewing more to the functional core/imperative shell idea. Redux is a popular approach that shows the power of this. In more modern languages with ADTs you don’t even need a library/framework once you fully understand how to structure things well.
It’s entirely possible I wasn’t able to appreciate it at the time, too.
What happens when it's one of hundreds of checkboxes on the site? When they all do slightly different things depending on user sku, product page, file type, file permissions, god knows what else. And when one experiment flag is on or how about this other one? Then i18n? Then what about the fact that after you write this line countless other engineers will see and modify it in the future, it's not just you writing and maintaining it? These are the problems that my company (and, I assume, Publicis Sapient with 20,000 employees according to Google) have to consider when choosing whether to hand roll JS or use a framework.
You'll still have to tackle those complex requirements and come up with a good design that organizes things and makes them manageable, whether you're using React and JS or just JS.
To say that a good design is just impossible with JS or that no other engineers could possibly understand the resulting code seems absurd.
Javascript is a Turing-complete language. It gives you a lot of design options. Your poor engineers will have to learn it to use React anyway so why would vanilla js necessarily be worse than js plus a giant framework?
Unless you don't need to reach that high.
In the case of single page apps, preventing you from shooting yourself in the foot is outside its scope.
What you are describing sounds more like a general problem in state management, and you'll have that with any framework. A common way to avoid such things in React applications is with Redux.
Unfortunately, even those advanced tools need to be learned and applied.
And when you use it that way, the way that everyone who has ever used React uses React, it can indeed cause problems of its own! Half the time, the only reason people build single-page apps is because they've destroyed their page-load performance by using heavy JavaScript frameworks! If you make a big nested component hierarchy, and then it turns out you need a component to affect something nine levels below it in the component hierarchy, and you have to modify all the intervening components or rig up a Context provider or something, that is a problem that has been caused by React's design! If you put a piece of state in Redux, and then it turns out you need to access it in a place that does not have easy access to the Redux store object, that is a problem that has been caused by Redux's design!
You can say "ah they just haven't learned to use the tools properly", but then we're back to noticing that anyone capable of using React to write good code doesn't need to use React to write good code.
There are tons of ways to access state in a redux store elegantly from anywhere in a javascript application. Yes, if you think that is the difficult part, you are clearly doing it wrong...
And please don't write single page applications when you don't know why you need one... There are plenty of reasons why SPAs can provide a better user experience. If you do it right.
Buidling the next Google Docs or Notion app? Use React/Angular, etc.
99% of the people that use React use it to build the former and then debate endlessly about their little teets and toots of JSX, Redux, Router library, etc. FFS, it is overkill and unnecessary. Simplify, go home on time and enjoy your the time with your family.
Wouldn't the performance of Google Docs suffer too much by using React? If I remember correctly they're switching to canvas-based rendering. Same thing with VSCode, they don't use a framework. I'm not sure about Notion, as I don't use it much, but I've never been impressed by the performance too. I think your best bet for React/Angular is around "medium complexity", so a typical web app that isn't that complex.
Working with any significant backend API, loading any images, or even your own javascript functionality takes you over that limit easily.
And then there is caching and CDNs...
In most cases, at least one of those is not true and the developers are using something like CRA so your site has a number of users who see tens of seconds of nothing while their Android phone downloads and runs a 1MB JS bundle. Bonus points if the marketing department has tossed in a tag manager which delays that even longer.
A 100-lb gorilla is...quite minimal, actually.
Meanwhile I have seen SO MANY sites that use full React for a contact form with two fields on it.
> Meanwhile I have seen SO MANY sites that use full React for a contact form with two fields on it.
I agree that the contact form you describe would be dead-easy to do vanilla. Maybe throw in Parcel if you want to write ES6.
However, while I suspect some people may use React in this case because it's all they know, others may do it because they are literally faster in React - they have a workflow that can see them code and deploy such a website to Netlify or Heroku or whatever in 10 minutes.
Why introduce inconsistency in everyone's workflow just because this specific page is a simple contact form?
The fact that it is just a simple contact form is exactly why you should use the same tools and workflow that everyone in your team has already been using.
Write what other people knows so you don't have to be the one maintaining it.
But:
1) Even though the web-specific example is contrived, it's a good demonstration/explanation of what people mean generally by declarative vs imperative
2) I don't want to wade into the tired argument, but classic HTML + minimal hand-written JS doesn't scale beyond a certain point. If it did, everyone would still be doing things that way
What are the chances that you are arriving at a solution that is better than React? That other engineers have no trouble understanding and maintaining? You'd have to invent new concepts, or at least call them differently, to not copy React's (or any other framerwork's) concepts or ideas. That would make it harder for other people to understand the code.
Anything moderately complex gets really painful when you’re storing state in all your HTML elements.
A big part of React is to abstract away the horribleness of trying to prevent unnecessary, quite expensive interactions with the DOM.
That’s why a lot of comparison examples miss the point and facilitate poor discussion.
var checkbox = document.querySelector(
"input[type=checkbox]"
), container = document.querySelector(
"#wrapper"
);
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
container.classList.add("checked");
} else {
container.classList.remove("checked");
}
});
Then use CSS to show or hide the message inside the container based on if it has class "checked" or not.You can get a whole lot done with very little code if you learn to take advantage of the three-legged stool of HTML, CSS and JavaScript.
container.classList.toggle('checked',checkbox.checked)Usually for things like that I'll come up with a CSS class that means "set it up so this element has its class toggled based on the state if the checkbox it contains". Sometimes I'll use data- attributes for additional configuration.
In all of my programming exp, back-end or front-end, "the platform" is what people try to restrict access to and abstract away so that they can think about the problems they're solving. Only on the web, after decades of putting up with a then crappy platform, now better, that developers now have a Stockholm syndrome for it.
> In all of my programming exp, back-end or front-end, "the platform" is what people try to restrict access to and abstract away so that they can think about the problems they're solving.
You're assuming that writing code for your abstraction will be faster than server-side rendered HTML with a sprinkle of JS. Sometimes it is, sometimes it's not.
Strawman, I didn't even say "fast" at any point, there are many other reasons platform should be abstracted away: better visibility of platform dependencies, easier security audit, portablity, ease of refactoring/deprecation of platform APIs
<input type="checkbox" id="discount">
<label for="discount"></label>
<style>
#discount + label:before {
content: "Click me to apply fake discount!";
}
#discount:checked + label:before {
content: "Click on me to remove fake discount";
}
#discount + label:after {
content: "You have not availed discount";
display: block;
}
#discount:checked + label:after {
content: "Discount Availed!";
}
</style> <!doctype html>
<body>
<custom-element></custom-element>
<script>
const CHECKBOX_ID = "my-checkbox";
const defaultLabelContent = "Toggle me, you newbies";
const beforeDiscountText = "You have not availabled discount";
const afterDiscountText = "Discount Availed!";
const beforeLabelText = "Click on me to remove fake discount"
const afterLabelText = "Click me to apply fake discount!"
const state = new WeakMap();
class CustomElement extends HTMLElement {
connectedCallback() {
this.render();
this.shadowRoot.addEventListener('change', (event) => {
state.set(this, event.target.checked);
this.render();
})
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
render() {
let isChecked = state.get(this);
const discountText = isChecked
? afterDiscountText
: beforeDiscountText;
const labelText = isChecked
? beforeLabelText
: afterLabelText;
this.shadowRoot.innerHTML = `
<input
${isChecked ? 'checked ' : ''}
type="checkbox"
id=${CHECKBOX_ID}
>
<label for=${CHECKBOX_ID}>
${labelText}
</label>
<div>${discountText}</div>
`;
}
}
customElements.define('custom-element', CustomElement);
</script>https://codepen.io/uwwgo/pen/GRmEKJz
(nothing revolutionary, does the exact same thing.)
Your comment is fully correct, but I would like to point out:
- with such a tiny DOM to rerender, it is equivalent, probably even faster
- custom element encapsulates DOM and it is fairly trivial and extremely fast to pick DOM (like you can even use id's everywhere) in the 'old jquery' way, with simple library functions
- updating DOM with simple render() call is way more elegant, but if you keep custom elements DOM small (and one should), this way is not that bad at all
Like this:
<!doctype html>
<body>
<custom-element></custom-element>
<script>
const CHECKBOX_ID = "my-checkbox";
const defaultLabelContent = "Toggle me, you newbies";
const beforeDiscountText = "You have not availabled discount";
const afterDiscountText = "Discount Availed!";
const beforeLabelText = "Click on me to remove fake discount"
const afterLabelText = "Click me to apply fake discount!"
const state = new WeakMap();
// 'library' code
function qs(selector) {
return this.shadowRoot.querySelector(selector);
}
function getElem(nodeOrSelector) {
return nodeOrSelector === String(nodeOrSelector) ? qs.call(this, nodeOrSelector) : nodeOrSelector;
}
function replaceText(nodeOrSelector, text) {
let elem = getElem.call(this, nodeOrSelector);
if (elem) elem.textContent = text;
}
function updateAttribute(nodeOrSelector, name, value = '') {
let elem = getElem.call(this, nodeOrSelector);
if (elem) elem[(value ? 'set' : 'remove') + 'Attribute'](name, value);
}
// end of 'library' code
class CustomElement extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<input type="checkbox" id=${CHECKBOX_ID}>
<label for=${CHECKBOX_ID}></label>
<div></div>
`;
this.shadowRoot.addEventListener('change', (event) => {
state.set(this, event.target.checked);
this.update();
})
this.update();
}
update() {
let isChecked = state.get(this);
updateAttribute.call(this, 'input', 'checked', isChecked);
replaceText.call(this, 'label', isChecked ? beforeLabelText : afterLabelText);
replaceText.call(this, 'div', isChecked ? afterDiscountText : beforeDiscountText);
}
}
customElements.define('custom-element', CustomElement);
</script>I hope you’re comparing this to React? Because if you’re comparing it to vanilla JS that reuses the DOM nodes, absolutely not.
This article is absolute nonsense from start to finish. What amounts to basically "lying" about the complexity of vanilla JS actually ends up mainly just doing a massive disservice to React which shouldn't need false comparisons to show it's benefits.
Maybe they're just being disengenuous but I got the distinct impression from many of the examples that the author doesn't actually know vanilla JS at all.
You have to invent some elaborate local state schema and then map it to DOM, instead of keeping part of the state implicitly stored in DOM, where it makes sense.
You can assign your data directly to DOM node objects. Say `el.yourData = todo_item`. And then let the order and list of items be kept implicitly in DOM, so you can do obvious things like remove nodes using `.remove()` reorder them via DnD, and whenever you need the list as data, you just querySelectorAll('.item').map(el => el.yourData) and you have your list. All quite straightforward.
You can also do context dependent things by being able to travel up to the ancestors via parentNode.
Declarative frameworks like react make simple things like this painful in comparison.
Something happens
The browser triggers and event and sends it to React
React calls your code to re-render the world[1]
React takes the new world order and does a diff
React calculates what to do go from old -> new and applies
In VanillaJS world you can do way way way better than this with the benefit of knowing how your application works. Something happens
The browser triggers and event and sends it to your handler
Your handler updates the DOM
It would be nice if in future you could make some promises to React about what happens on an event so React can skip the diffing step and just update the virtual and real DOM directly. And hint hint if you can generate the code to update the DOM directly in the general case too then do you even need the virtual DOM? Svelte is doing really cool work in this space.[1] componentDidUpdate helps but same diff
I'm glad it's not easy to escape the paradigm.
I know there are cases where it's needed but it's rare (at least in my line of work).
I found this ironic, as I have never in my life had more struggles with state, than when working with react native. Seriously, the amount of times something didn't update, or was updating way too many times, is countless. It's so easy to shoot yourself in the foot. Imperative native programing, almost never have this issue.
Overall I prefer React because I prefer simple composable libraries over frameworks and Angular reminds me too much of programming in Java with its long application boot times and its folders full of ListUpdateCheckerConditionFactoryBeanUnitTest.java's.
*To be fair, in an equivalent React app, at that point you would have had to write a whole lot of shouldComponentUpdate to not tank performance.
The React haters here seem to think that is easy. But most of the popular frameworks/libraries use a virtual DOM in some way or another. And Svelte also automates handling the updating. Maybe it's not so easy after all, if smart people have spent decades trying to find alternatives.
This is what Svelte's doing, as I understand it: letting you think like you're writing React ("when in this state, the world should look like this") but avoiding the (greatly overstated, IMO) overhead of the virtual DOM by translating the changes into direct DOM updates in a complilation step.
I think I blame part of this on the language we use. These days a huge buzzword of sorts is the “zero-cost abstraction.” It means zero runtime cost, but a lot of us internalize it as completely free, failing to account for the fact that it often entails increased complexity and build times. Granted, often this is a worthwhile tradeoff, but in order to know you need a more nuanced comparison. That virtual DOM is “pure overhead” isn’t objective fact - pure overhead is an error of coding that can be fixed in a pull request. No, this is more like ideology.
The problem with this ideology isn’t that it’s bad, it’s just incomplete. It usually explains away why the world hasn’t simply adopted their worldview by appealing to ignorance or even stupidity, while ignoring potentially serious issues. Like yeah, if I want to make a desktop application in 2021, I have to consider Electron because simply put, there’s a lot broken in modern native UI.
And sometimes people are still right. There’s always a possibility Svelte will win out, or eventually be proven right even if it doesn’t.
Maybe. But it’s 2021, and the year of the Linux desktop is still around the corner. If something hasn’t happened yet, at least humor some reasons why that might be the case.
(Personally, in my experience, even update-intense apps wind up having bigger fish to fry than VDOM, so I can’t say I’m holding my breath.)
Even though JSX is pretty React-specific, it still has managed to make its way into other frameworks and many compilers because it's fairly general at its core and because it is a fairly non-invasive process. So for example, you can compile JSX using babel, but also TypeScript, esbuild, and more. (And those are all independent implementations!)
I can't claim to know what the build process looks like for Svelte, but I have a suspicion it is far less general than the source code transformations that we have so far that provide type checking and newer ES functionality in older engines. (Which, by the way, you appear to be insinuating are not "actually useful.") Angular has a similar thing going on and it is not terribly well received.
But in reality, the virtual DOM allows you to avoid manipulating the actual DOM, which is both slow and error-prone. The browser necessarily attaches all sorts of memory objects and state to components in the DOM, which his why unnecessary mutating has to be avoided at almost all cost.
Svelte may go a step further by automating DOM manipulation. But it doesn't have the same following yet. And usually, if there is no explosive growth in such a technology, it's not just because of evil overlords preventing its justful rise...
There's also the preact method which diffs against the DOM directly, so that overhead goes away.
Do you remember when InfernoJS broke the framework test? It blew away 2-3 versions of vanillaJS with it's vdom implementation. They literally tore apart what it was doing in order to finally create a vanilla version that was faster, but very unmaintainable.
Today, "native" frameworks like Svelte are only fractionally faster at synthetics. At the same time, each component contains a brand new set of functions which has two major drawbacks. First, code size grows at F x N (where F is framework size and N is your normal code) rather than F + N. Linked to this is the performance implications. Once the JIT warms up the vDOM code (almost instantly), the user gets max performance for the most critical loops when visiting pages for the first time. With "native" frameworks, each new page means switching back to completely unoptimized code.
However, in practice the difference hardly matters and it is really hard to do efficient DOM mutation correctly. Which is why virtual DOM approaches are at the heart of most of the modern frameworks...
you can get pretty darn close to the react version of this example using vanilla js.
If you only mutate state on the server and simply send views back down to the clients, nearly all of "modern" web development practices can be safely ignored.
Clearly, there are difficult engineering challenges with the approach of "all state on the server all the time", but anything worthwhile is never easy. Down this path you might not obtain Netflix-tier webscale directly out of the box, but that doesn't mean we throw our hands up and have all our clients drown in multiple megabytes of angular11+ dependency trash either.
How many internet users are more than 50ms away from a Cloudflare/Microsoft/Google/Netflix CDN in 2021? How many cores can you get in a 1U server now? Distributed state machines for client UIs have been a massive mistake.
I think it's important to define early in a project if the app is "cloud-based" or "local-based". If it's cloud-based, then only the server mutates state and sends views to the clients, as you wrote. Everything is simple. If it's local-based, then all data are available locally and mutated locally. The cloud is only used as mirror/backup and can support for some advanced features like full-text search. That's also a very clear architecture. That's how Linear works.
Things become hairy and unnecessarily complex when no clear choice is made between those two options, then it's not clear where the state is.
There are real world challenges. There's no internet access on the NYC subway. Latency can spike for no apparent reason. Making a webapp a dumb client really only works for wired connections on an intranet.
When I was working in South Korea, their subway system had excellent reception to cellular and even satellite TV networks. They explicitly engineered a solution to this problem in direct, first order terms.
This is precisely the type of approach we should be taking in my opinion. Make the networks robust, not the client-facing web apps. We are spending engineering resources in largely the wrong places.
This would be like arguing for a car that can hover, but only for an ambiguous/non-specific amount of time, simply because the roads are occasionally total shit.
I try my best to build robust apps but have no bearing on my countries infrastructure plans. And have the government reallocate my resource to setting up antennae in tunnels is dystopian.
I completely agree. Sharing a layer of an application between client and server is a nightmare and will fly in your face.
That said, it is possible to cleanly handle more involved interaction on the FE and have a clean separation of responsibilities. When well done, the results are very snappy and robust. Of course, it isn't always necessary, and most RoR applications beat that 5mb Angular behemoth.
I wish software developers would be more honest about their tools, weighing their pros and cons, instead of thinking up contrived 'examples' that paint them in the best possible light.
Every library/tool/framework will make you think "well, this sucks" at some point. So be honest.
If anything, this article is an example of how you need to write and truly understand vanilla JS (and learn form your mistakes) before you can appreciate and understand what react had to offer when it first came out.
I might have to write a counter blog post just to balance this out...SMH.
I remember using jQuery, Backbone/underscore, handlebars, knockout, Angular 1, Enyo, Ember, and probably a couple others. Angular in particular had like 70% market share among major frameworks and almost all the libraries didn't support React.
React took over because it was head and shoulders better than everything that went before and taking days to learn instead of weeks or even months.
The reason nothing has taken over since is because we've reached the point where they aren't good enough to justify throwing away all the existing library support.
And yet this article is disingenuous. The OPs target can be achieved using CSS alone. In fact, quite a lot of things can be done quite well in vanilla JS and CSS.
This works well to some degree. Then I found it to fall apart.
- Those BE devs now forced to write FE in React|Vue|* and hate it so much that they've never learned it, won't learn a vanilla stack either. Especially not CSS. That's just for colours. Or something.
- Most vanilla approaches I worked on relied heavily on the correct ordering of HTML elements with the correct classes for something to work. There's little to no encapsulation. Only copy and paste. It's also immensely brittle.
- You can add some simple encapsulation, but the easier way is to brute force some imperative mess. Close ticket. Open next. Velocity is high.
- The next step is to build your own framework. But chances are it will be worse than any 3rd party solution.
- A huge advantage of 3rd party libs is that there's zero chance of some ticket specific code to erroneously make its way in to the framework. Silos aren't always bad.
- Onboarding new devs is hard. There's probably no resources at hand.
- At this point you've tied yourself in to knots and the actual problem, your product, isn't even close to being solved.
It takes less than an hour to have a fast React|Vue|* application off the ground. Everything you need to build the product is in place. And with some care, it will remain fast. Alternatively, Ruby on Rails and friends work just as well.
However, wasting endless hours reinventing a worse wheel is unproductive.
For a real "ground up" approach of explaining those I'd recommend this article series: https://acko.net/blog/climbing-mt-effect/
Your heard me correctly, you can create components with JavaScript without the aide of a framework.
But once you have to display lists of stuff with lists of other stuff inside you basically need a template library otherwise you're going to be doing some pretty weird DOM manipulation to copy sections of elements.
So a comparison that might resonate better with folks here might be to have a pure JS TODO app with a comment section in each list item. It becomes harder to express in pure HTML. You'll want some template library to keep it maintainable.
If your lists are long enough, you probably want to split what you can actually display from the rest of the list, because even today updating the DOM with thousands of nodes is expensive. And doing seems to be easier and less chatty to do on the front-end I think.
Reminds me of all the "yaml is better than XML/JSON" examples where everything is carefully formatted in a way that make no sense unless you are trying to prove a point.
Part of me think we should just ignore html and just render everything using webgl/webvulkan/whatever instead of fighting the browser's flow. We're not there yet but things have been moving in that direction because it kind of makes sense
I lived thru the Java AWT to Swing transition. One of Swing's Big Ideas was for every component (aka view & controller) to be backed by a separate model, finally mainstreaming the MVC design pattern.
Then of course all the web frameworks got nutty over MVC.
My only takeaway from the many, many MVC debates is that the only way to win is to not play.
At least vanilla js version doesn't re-render the whole component.