What if React was imperative instead of being functional?
When you write JSX, it compiles down to nested calls to createElement()
(or the equivalent, I don't know if that's still the function React uses). Because this ends up being a single recursive function call, you can only use value expressions inside the code--that's why you have to map()
when you loop, and you have to use ternaries instead of actual conditional statements.
Over time, the React team's thinking about architecture has clearly shifted a lot, to the point now where hook functions create implicit, hidden state that's preserved in the order of the function calls. What if JSX worked this way? What if when you wrote a template, instead of a single function call with nested expressions, it used imperative, IMGUI-like function calls instead? In that case, the raw code might look something like this:
template(html => {
var { footer, a, button, text, div, ul, li } = html;
div`.outer`;
div`.container`;
button`.toggle`({ "aria-pressed": false });
text("hello");
div.close();
ul`.second`;
for (var i = 0; i < 3; i++) {
li();
a`.page`({ href: "#" + i });
// console.log(link);
text("Page " + (i + 1));
li.close();
}
div.close();
footer();
text("footer section goes here");
});
Which creates:
<div class="outer">
<div class="container">
<button class="toggle" aria-pressed="false">hello</button>
</div>
<ul class="second">
<li><a class="page" href="#0">Page 1</a></li>
<li><a class="page" href="#1">Page 2</a></li>
<li><a class="page" href="#2">Page 3</a></li>
</ul>
</div>
<footer>footer section goes here</footer>
The advantage is that you now actually have all the power of JS keywords and syntax, including regular loops, conditional statements, and potentially async/await. The bad news is that it's an abomination before god and man.
Code for generating this follows:
function template(callback) {
var template = document.createElement("template");
var branch = template.content;
var tagFunction = function(tagName) {
var update = function(input) {
if (!input) {
element(true);
} else if (input instanceof Array) {
var classes = input.join("").split(".").filter(c => c);
element(true).classList.add(...classes);
} else {
for (var k in input) {
element().setAttribute(k, input[k]);
}
}
return update;
};
var element = function(force) {
if (force || !update.element) {
console.log(`Creating ${tagName}...`);
update.element = document.createElement(tagName);
}
if (branch != update.element) {
console.log(`Appending...`);
branch.append(update.element);
branch = update.element;
}
return update.element;
};
update.close = function() {
if (update.element && update.element.parentElement) {
branch = update.element.parentElement;
}
}
return update;
};
var proxy = new Proxy({}, {
get(_, tagName) {
switch (tagName) {
case "text":
return str => branch.append(str);
default:
var tag = tagFunction(tagName);
tag.close = function() {
var closest = branch.closest(tagName);
var parent = closest && closest.parentElement ? closest.parentElement : template.content;
branch = parent;
};
return tag;
}
}
});
callback(proxy);
var clone = () => template.content.cloneNode(true).children;
return { template, clone };
}