9 ms·
Returning actual DOM nodes entirely blunts the big advantage of JSX (and non-JSX libraries like Lit) - which is their immediate mode style API, and UI=f(state)
by spankalee 2y ago
Returning actual DOM nodes entirely blunts the big advantage of JSX (and non-JSX libraries like Lit) - which is their immediate mode style API, and UI=f(state) model.
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).
- recursive 2y ago> 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. 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)
- spankalee 2y agoYou don't need diffing or reconciliation to turn a description of DOM into DOM. Lit works without a VDOM. 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?
- lolinder 2y ago1) Type safety for element props. 2) Autocomplete for element props. 3) IDE support such as refactors and jump to definition/jump to usages. 4) Proper syntax highlighting out of the box instead of the editor just saying "there's a string here". 5) A uniform pattern for defining custom components that work the same as primitives, rather than defining custom components as helper functions returning string fragments or something like that. And so on. JSX has a lot going for it regardless of the semantics chosen. It's just a syntax that is very convenient for lots of kinds of tooling, and it's completely unopinated about the semantic context in which it is used.
- _heimdall 2y agoThese are definitely helpful, but what you are describing are all language tool features rather than features of JSX itself. 5 would be the exception, but that is just user preference of what kind of syntax one likes to write components with.
- lolinder 2y agoWell, yes. But OP was asking about what makes this better than `innerHTML`, and the obvious answer is that support for HTML programming embedded in JavaScript strings is generally bad while support for JSX is very good across all editors.
- recursive 2y agoYou can have JSX that produces DOM nodes or "light-weight element descriptions". You can have imperative event listeners and updates. These are two independent dimensions. I made UI framework called mutraction that produces real DOM elements from JSX expressions. It also updates any contents or attributes of those DOM nodes based on their dependencies without requiring imperative DOM interaction from application code. https://github.com/tomtheisen/mutraction https://github.com/tomtheisen/mutraction Here's a click counter. `track()`, as you might guess creates a proxy so that reads and writes can be converted into dependencies. const model = track({ clicks: 0}); const app = ( <button onclick={() => ++model.clicks }> { model.clicks } clicks </button> ); document.body.append(app);
- ericmcer 2y agoIf you work on a team of suitable size I would hesitate to not leverage a VDom. I trust myself to not trigger dumb reflows, but the way I see a lot of people using React this could be a perf nightmare.
- acdha 2y agoI think that hits the real problem: it’s staffing and culture, not the tool. The 90th percentile site using a vDOM is also a perf nightmare, especially for anyone who doesn’t have a recent Apple device on a fast network, and that often has big impacts on usability and accessibility as well (dynamic loading sucks on screen readers unless you put way more time into it that most people do). I was painfully reminded of that while visiting Europe last month on a data plan which was clearly deprioritized on partner networks - the old sites with .php in the URLs loaded in a few seconds and worked perfectly, but every time something failed to load in less than 5 minutes or partially loaded but didn’t work a quick trip over to webpagetest.org showed a lot of NextJS, React, et al. scripts trickling in because clearly a form with half a dozen fields needs 25MB of JavaScript. The root cause is obvious: you get what you measure. If businesses prioritize Jira tickets closed per day, they’re going to get this soup of things promising to be easy to use for high developer velocity and they’re never going to get around to the optimizing it. If they’re trying to be able to hire as cheaply as possible, they’re going to look for the current tool boot camps are pushing and hire based on that, not looking for deeper knowledge of web standards or experience which costs more and shrink the candidate pool. If they’re looking for a safe choice, Facebook’s marketing means all of the big consulting companies will push React and few people will pause long enough to ask whether they’re building the same kind of apps it was designed to build (long session times, tons of local state being mutated, etc.) or whether they’re willing to invest the time needed to get it to perform reliably and well.
- MrJohz 2y agoThere are more and more frameworks that avoid the VDOM but still resolve this fine. As long as DOM mutation is handled by the framework (and not done ad-hoc), and as long as the framework has a mechanism for deferring those mutations until after all reads, then there shouldn't be a problem. This is the approach taken by SolidJS, Svelte, and even the new rendering model for VueJS. In all these frameworks, mutations happen inside effects, and effects are scheduled such that they all happen at the end of the tick, avoiding reflows and thrashing.
- JasonSage 2y agoI agree with the sibling comment that this really depends on the user. To take a different approach: JSX is just a different DSL to the createElement function call pattern (see Preact.h for example) and all of the benefits you’re describing come from the framework and runtime. 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.
- spoonfeeder006 2y ago> For example at my last company we used JSX to make test data factories 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?
- deleted 2y ago[deleted]
- LAC-Tech 2y agoReturning actual DOM nodes entirely blunts the big advantage of JSX (and non-JSX libraries like Lit) - which is their immediate mode style API, and UI=f(state) model. 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.
- littlestymaar 2y agoYeah, as much as I liked the idea of an “immediate mode API which is in fact retained under the hood which makes things both ergonomic and performant”, the reality is that React failed to deliver on that and every sufficiently big app ends up having performance problems that are then fixed by opting out of the immediate mode illusion.
- austin-cheney 2y agoWhether you like this project or not, your comment so completely misses the point. You are confusing the JSX syntax, which is what the author wanted by extracting it away from React, for all the React candy. This is a missing the forest for the trees kind of thing. 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.
- SkiFire13 2y agoState management is not simple. You have to constantly keep in sync two different piece of states (your data model and the UI). Making sure that when you modify some parts of your model then everything depending on that is also updated is one of the hardest things to guarantee. Fundamentally this is because it is non-local: you cannot tell what will change by just looking at the definition of what you're mutating. 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.
- austin-cheney 2y agoStop the madness. Its stupendously simple. As the user, not you the author of some code, make changes to the UI it will be via events. In the respective event handlers just update a big state object with the identity of the node and how it is changed. When the page loads simply read from that state object to recreate the respective node exactly how they were when the user modified them. That's it. Nothing more. Its how I wrote a full OS gui for the browser. It does far more than SPA framework, does it substantially faster, and requires only a tiny fraction of the code. Yes, yes, here come all the excuses, like: I can't code or it wont work in a team, because other people cant code. Stop with the foolishness. Provide some governance, preferably fully automated, around event handling and you have complete obsoleted your giant nightmare framework. I describe it here: https://github.com/prettydiff/wisdom/blob/master/state_management.md https://github.com/prettydiff/wisdom/blob/master/state_manag...
- novocantico 2y ago> is probably the main benefit of a VDOM 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.
- nine_k 2y agoIf you have little state, or simple uniform state, you can actually store it in the real DOM efficiently, as values of controls, or lists of similar DOM nodes under a common known parent. If most of your DOM is static, and you only need small bits of interactivity, React is an excessively heavy tool. 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.
- newzisforsukas 2y agothere is no reason something imperative cannot be declarative. the war is one of style, not capability, so saying you gain "fine-grained control" is kind of meaningless, imo
- deleted 2y ago[deleted]
- sieabahlpark 2y ago[dead]
- deleted 2y ago[deleted]
- francasso 2y agoFrom my experience creating complex web UIs, the performance angle of using a vdom is pure fantasy if your application is complex enough. 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.
- kristiandupont 2y agoAnd from my experience building complex web UIs, those team members were right -- there are many ways to avoid the issues and using vdom is great in general. True, there are situations where it falls short, which is why you will want to fall back to other techniques for those bits of architecture. Just like your JS, Python or Ruby server will call a bunch of functions written in C or the like. That doesn't mean you should write your entire backend in C.
- littlestymaar 2y agoYes, there are ways to avoid the issues, and they involve abandoning the immediate mode illusion that react created in the name of simplicity.
- johnfn 2y agoWrite it in React, and if you run into performance issues, there are a bunch of well-known performance optimizations you can make which are easy to discover. It's a well-trodden path that many engineers have walked before. Write it in your own vanilla framework, and you will effectively re-invent all the complexity of React, but in a way that no one has ever done before. It's easy at small application scales, but once your app gets large, good luck debugging the thing that exists primarily in your principal engineer's head.
- dminik 2y agoThis really just isn't true. If your state updates are at the component subtree level (like react) a vdom is a good choice. But, if you make your state changes more granular, you can get away with skipping VDOM entirely and work with just* regular dom nodes. Look at Solid or Svelte. No VDOM there, just pure granular updates. *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.
- SkiFire13 2y agoTBF while Solid and Svelte don't use a VDOM on which they perform diffing, they still ultimately create a tree parallel to the DOM which is used to track dependencies.
- AltruisticGapHN 2y agoI'm having fun using lit-html with vanillajs, after I saw a tweet from Marc Grabanski suggesting he didn't use the full Lit library. Admittedly I am then not taking advantage of all the reactivity goodness, but I also really dislike the decorators syntax and 95% of the time I just don't need the reactivity after the first render. 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?
- croes 2y agoSvelte and SolidJs work pretty well without VDOM
- kolme 2y agoNice, I love lit-html(1)! 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!
- skrebbel 2y agoJSX in SolidJS directly returns DOM elements much like in the top part of this post, yet it does not have these disadvantages. It's true that strictly put it's not immediate mode like React and Lit are, but the framework is designed such that there's few practical downsides to that.
- PKop 2y agoUI is not a pure function of state[0], "UI state" is relatively stable and does not have to be recreated constantly when data input changes. [0] https://blog.metaobject.com/2018/12/uis-are-not-pure-functions-of-model.html 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.
- lucideer 2y agoIf the above is what you need there's not strong reasons not to use React or similar. But for most things that will lead to an interest in "Vanilla JSX" this line of thinking is premature optimization - the advantages of vdom are extant but enormously overstated. JSX has many other advantages. 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.