Reactive DOM
Jin
Posted on March 4, 2024
Previously, the DOM was slow and inconvenient. To cope with this, various template engines and techniques were invented: VirtualDOM, IncrementalDOM, ShadowDOM. However, the fundamental problems of RealDOM remain:
- Greed. The browser cannot at any time ask the application code “I want to render this part of the page, generate me elements from the middle of the fifth to the end of the seventh.” We have to first generate a huge DOM so that the browser only shows a small part of it. And this is extremely resource-intensive.
- Indifference. The state of the DOM logically depends on both application states and the states of the DOM itself. But the browser doesn't understand these dependencies, can't guarantee them, and can't optimize DOM updates.
- Thorny. In fact, we don't need the DOM. We need a way to tell the browser how and when to render our components.
Well, okay, let's imagine what would happen if the DOM and the rest of the runtime were reactive. We could, without any libraries, connect any states through simple invariants and the browser would guarantee their execution in the most optimal way!
I sketched out a small sketch of what it might look like. For example, let's take and bind the paragraph text to the value of the input field:
<input id="input" />
<p id="output"></p>
<script>
const input = document.getElementById('input')
const output = document.getElementById('output')
Object.defineProperty( output, 'innerText', {
get: ()=> 'Hello ' + input.value
} )
</script>
And that's it, no libraries, no event handlers, no DOM manipulation. Only our desires in their purest form.
Do you want to try ReactiveDOM in action right now? I published a prototype of the polyfill $mol_wire_dom.
It is not very efficient, it does not support many things, but for demonstration it will do:
<div id="root">
<div id="form">
<input id="nickname" value="Jin" />
<button id="clear">Clear</button>
<label>
<input id="greet" type="checkbox" /> Greet
</label>
</div>
<p id="greeting">...</p>
</div>
import { $mol_wire_dom, $mol_wire_patch } from "mol_wire_dom";
// Make DOM reactive
$mol_wire_dom(document.body);
// Make globals reactive
$mol_wire_patch(globalThis);
// Take references to elements
const root = document.getElementById("root") as HTMLDivElement;
const form = document.getElementById("form") as HTMLDivElement;
const nickname = document.getElementById("nickname") as HTMLInputElement;
const greet = document.getElementById("greet") as HTMLInputElement;
const greeting = document.getElementById("greeting") as HTMLParagraphElement;
const clear = document.getElementById("clear") as HTMLButtonElement;
// Setup invariants
Object.assign(root, {
childNodes: () => (greet.checked ? [form, greeting] : [form]),
style: () => ({
zoom: 1 / devicePixelRatio
})
});
Object.assign(greeting, {
textContent: () => `Hello ${nickname.value}!`
});
// Set up handlers
clear.onclick = () => (nickname.value = "");
Here we also applied $mol_wire_patch
to make global properties reactive. Therefore, when the browser zoom changes, the interface size will change to compensate. When you click the button, the name entered in the field will be cleared. And the current name will be displayed in the greeting, which is shown only when the checkbox is checked.
Now answer me a simple question: why don’t we still have something like this in browsers?!?
Posted on March 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.