I don't particularly like how control logic needs to be embedded within JSX using ?ternary : operators and .map(() => stuff) within the HTML.
Also, in order to transform JSX into individual rerunnable functions, we'd need a whole different transpiler. I like being able to code browser-runnable JavaScript directly.
To each their own. :-)
> I don't particularly like how control logic needs to be embedded within JSX using ?ternary : operators and .map(() => stuff) within the HTML.
It doesn't.
In my[1] framework, there is JSX, but control flow like map is done with a function.
<ul>
{ ForEach(model, item => <li>Fruit: { item }</li>) }
</ul>
There is a lambda there, yes, but at the top level it's a ForEach() function call.Likewise, it is possible to use get conditional elements in JSX without using react's ugly approach.
You can just write an IIFE expression, and use regular imperative logic like if/else and switch statements.
If you find the syntax ugly, you can create a function like "run(expr)" to wrap it, similar to Kotlin's method of the same thing.
<div>
{(() => {
switch (status) {
case Status.LOADING: return <div className="loading">Loading...</div>
case Status.ERROR: return <div className="error">Error: {error.message}</div>
case Status.SUCCESS: return <MyComponent data={data} />
}
})()}
</div>
> Also, in order to transform JSX into individual rerunnable functions, we'd need a whole different transpiler.
I don't think you would. `<Component prop="example" />` gets converted by current transpilers into `jsx(Component, { prop: "example" })`. The `Component` itself is passed as is. In the case of Components that are just functions, that passes the function as-is, as a function you can just call as needed.
JSX was built for "rerunnable functions". It's a lot of how React works under the hood.
The problem is that JSX transpilers will put child nodes in an array, instead of in an anonymous function, meaning there is no easy way (without transpiler magic) to rerender just a part of the component.
This JSX:
<section><h1>Welcome</h1>{data.enabled ? <input /> : "disabled"}</section>
Which becomes this with Babel: _jsx("section", {children: [
_jsx("h1", {children: "Welcome"}),
data.enabled ? _jsx("input", {}) : "disabled"
]})
But we'd need something like this to fit Aberdeen: _jsx("section", ()=>{
_jsx("h1", ()=>{_jsx("Welcome");});
if (data.enabled) _jsx("input", {}) else _jsx("disabled");
}})
In React you can, with some effort, limit virtual DOM rerenders to a single component. Aberdeen rerenders fractions of components by default, if that's all that needs to happen. That's why it works well operating directly on the actual DOM, without a virtual DOM inbetween. Your `_jsx` function can auto-wrap children in a function. Also, you can just pass functions as children, too if you really want to make the JSX author work for it:
<section><h1>Welcome</h1>{ () => data.enabled ? <input /> : "disabled" }</section>
That doesn't even look half bad to me. It looks really close to many of your other examples.> In React you can, with some effort, limit virtual DOM rerenders to a single component. Aberdeen rerenders fractions of components by default, if that's all that needs to happen. That's why it works well operating directly on the actual DOM, without a virtual DOM inbetween.
A lot of that depends on what your model of a "component" is. React will rerender fractions of a component in advanced areas such as a Error Boundaries and Suspense.
Also, for what little it is worth, my JSX-based library isn't a virtual DOM, operates directly on the actual DOM, and generally renders components once and only once in their lifetime, because it binds all changes even more directly to specific DOM properties.
Modern react developers forget that if statements exist. When react was class based it was trivial to do:
render() {
if (this.dontNeedToRender) {
return null
}
}
Now, because hooks can't be skipped, react developers jump through many hoops to use an if statement during rendering. I might be missing your point, can you elaborate? If you want to write an if statement you just do it at the end of a component, after the hooks. It's a common pattern.
You don't have to do that in the html if you don't want to. You can easily assign those operations to a variable then insert the variable into the html.
re: syntax, I agree, it's stopped me from ever trying React/JSX-based frameworks, which I am sure is an over-reaction.
I have a POC syntax extension (babel parser fork) I named JSXG where I introduced "generator elements" which treats the body of the element as a JS generator function that yields JSX elements.
The simple/naive implementation of just running the generator was okay, but I (perhaps prematurely) worried that it would be not ideal to have the resulting list of child elements be actually dynamic-- as opposed to being fixed size but have false/null in place of "empty" slots and also using arrays for lists made by loops.
So, I also had a transform that followed conditional branching and loops etc. and made a template of "slots" and that resulted in a stable count of children, and that improved things a whole lot.
It's been a while since I revisited that, I should try and find it!
Comparisons below.
Aberdeen:
$('div', () => {
if (user.loggedIn) {
$('button.outline:Logout', {
click: () => user.loggedIn = false
});
} else {
$('button:Login', {
click: () => user.loggedIn = true
});
}
});
$('div.row.wide', {$marginTop: '1em'}, () => {
$('div.box:By key', () => {
onEach(pairs, (value, key) => {
$(`li:${key}: ${value}`)
});
})
$('div.box:By desc value', () => {
onEach(pairs, (value, key) => {
$(`li:${key}: ${value}`)
}, value => invertString(value));
})
})
JSX: <div>
{user.loggedIn ? (
<button
className="outline"
onClick={() => user.loggedIn = false}
>
Logout
</button>
) : (
<button onClick={() => user.loggedIn = true}>
Login
</button>
)}
</div>
<div
className="row wide"
style={{ marginTop: '1em' }}
>
<div className="box">
By key
<ul>
{Object.entries(pairs).map(([key, value]) => (
<li key={key}>
{key}: {value}
</li>
))}
</ul>
</div>
<div className="box">
By desc value
<ul>
{Object.entries(pairs).map(([key, value]) => (
<li key={key}>
{key}: {invertString(value)}
</li>
))}
</ul>
</div>
</div>
JSXG: <*div>
if (user.loggedIn) {
yield <button
className="outline"
onClick={() => user.loggedIn = false}
>
Logout
</button>
} else {
yield <button onClick={() => user.loggedIn = true}>
Login
</button>
}
</*div>
<div
className="row wide"
style={{ marginTop: '1em' }}
>
<div className="box">
By key
<*ul>
for (const [key, value] of Object.entries(pairs)) {
yield <li key={key}>
{key}: {value}
</li>
}
</*ul>
</div>
<div className="box">
By desc value
<*ul>
for (const [key, value] of Object.entries(pairs)) {
yield <li key={key}>
{key}: {invertString(value)}
</li>
}
</*ul>
</div>
</div>
Edit:Come to think of it, I think it may have been <div*>...</div*> or even (:O gasp) <div*>...</div>.
That looks cool!
I think it would be pretty easy to transpile this (or something like it) to code Aberdeen can consume. In fact, it would probably be a lot easier than transpiling for React, as Aberdeen uses immediate mode the `for` problem in your comment below wouldn't be a problem at all, so no return values to worry about.
I'd use something like this myself for larger projects, I think. But asking other developers to use a different programming language just to use your library usually doesn't fly well. :-) Coming to think of it, I find it actually kind of surprising that JSX succeeded.
Ah, the roadblocks are coming back to me.
The first was using </* as the marker for the end tag of a JSXG element. It worked, but it seemed like it wouldn't be too well received as parsers today treat /* as a comment in that spot iirc.
Edit: The other premature concern/feature was the ability to have a for loop NOT render to an array and rather be inline.
Normaly
for (...) { yield <>...</> }
// becomes
[[<>...</>, <>...</>, ...]]
But I added a mechanism using string directives that would inline it: "use jsxg:inline"
for (...) { yield <>...</> }
// becomes
[<>...</>, <>...</>, ...]