-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Aggressive JSX Do Expression Proposal #88
Comments
2+4; it seems weird to allow 2+4; it means you cannot move logic from outside of elements to inside of elements and vice-versa without refactoring the code. if (cond) {
return <Foo />;
}
let user = getUser();
return <User foo={user} />; <div>{
if (cond) {
<Foo />;
} else {
let user = getUser();
<User foo={user} />;
}
}</div> It's certainly not the end of the world, but in the second example it would be nice if I could just copy-paste the code into a function and be done with it. In the first example I can just call the function as Could it be an idea to assume/advocate a do-expression/everything-inside-a-fragment style for writing JSX? function render() {
return (
<div>{
if (cond) {
<Foo />;
} else {
let user = getUser();
<User foo={user} />;
}
}</div>
);
} Can now be trivially refactored into: function foobar() {
return <>{
if (cond) {
<Foo />;
} else {
let user = getUser();
<User foo={user} />;
}
}</>;
}
function render() {
return (
<div>
{foobar()}
</div>
);
} Where This is rather off-topic in some sense and it may not be a good idea to continue this particular discussion in here. So I leave that up to you so that this issue doesn't derail from its technical questions. |
To phrase it differently I assume that I think the fragment solution is neat. It does allow a way to opt in to this new style. However, my hope is that it doesn't end there but that we'll able to have a more general solution to this. Either by making JSX expressions in last position have implicit returns like that other thread, or do-arrows. let Foo = () => do {
...
}; |
I would assume the opposite actually; you can't function-return inside an expression so why should you be able to function-return inside a do-expression. But I guess reusing
Depending on the exact details of these JSX features, your do-arrow above would be equivalent to Having the last statement be always be implicit return inside functions is kind of strange, but useful. Inside |
Is there precedent for disallowing For the loop behavior, personally I think that it's a little too far to jump to looks collecting their results into an array. At that point you're introducing your own comprehension syntax, except it looks exactly like normal JS code other than being in completion location. Why is this more desirable than the existing I may be missing something, but why is calling out |
Rust has a different semantic than what @syranide expected and thought was the more useful semantic. It seems like there are at least three plausible options here but starting disallowed at least opens up for making the other choice later. Especially given that this use case isn't critical nor even that common in the context of JSX.
Basically there's two control flows from templating languages that people are missing. Now I think that what people really ask for is to express these things as elements. I'm not sure if that's because there's inherent value is the brackets or if that's just because lack of other ideas. Handlebars and such don't use elements for this but has specialized syntax. I do think that element based control flow is a bit too far away from the spirit of JSX. So this is an attempt at solving it without going down that route. That said, this is a kind of compromise that may not solve the core of the problem.
I agree, it's a big leap. I was considering comprehensions but I think that is a much bigger can of worms. It also is much further way from what people consider intuitive syntax. Meanwhile whenever we talk about do-expressions, people seem to assume that for-loops will work so it seems it is strangely intuitive.
Since other control-flow is not allowed, it may seems like |
I'm getting more convinced that The argument for comprehensions is usually to make it easy to write in a functional compositional style and make it approachable. E.g. nicer than writing So why push better I think the problem to solve is to make the transition from simple loops to imperative loops easy. With temporary variables. I'm not sure implicit return values from <div>{
for (let item of items) {
<Item item={item} />;
}
}</div> -> <div>{
let active = [];
let inactive = [];
for (let item of items) {
if (item.active) {
active.push(<ActiveItem item={item} />);
} else {
inactive.push(<Item item={item} />);
}
}
<>
{active}
{inactive}
<>
}</div> This works but the edge cases are pretty confusing because 1) you can't reference the variables let active = [];
let inactive = [];
<div>
{
for (let item of items) {
if (item.active) {
active.push(<ActiveItem item={item} />);
} else {
inactive.push(<Item item={item} />);
}
}
}
{active}
{inactive}
</div> However, the alternative of forcing people to write the <div>{
let allItems = [];
for (let item of items) {
allItems.push(<Item item={item} />);
}
allItems;
}</div> I wish this could be simplified just a little bit to make the transition to the complex example more natural while still letting the simple example remain concise. |
Maybe they should be implicit immediately invoked generator functions? <div>{
for (let item of items) {
yield <Item item={item} />;
}
}</div> Where the completion value is an implicit return at the end. |
@sebmarkbage Something along those lines makes most sense to me, having to allocate the array and return it is what adds most overhead. I still think being explicit about when you're adding to the array is for the better, IMHO it's not really nuisance either. Being able to effortlessly push, or in your example On a tangent, I personally would apply a similar reasoning to the discussed do-expressions as well. I wouldn't mind having to write I'm not personally a fan of the functional style for templates; it tends to only be helpful to a point which rarely covers the vast majority of the cases IMHO.
Self-executing or called by React? PS. Did you just thumbs up your own comment? 😄 |
@sebmarkbage Seriously, I had this morning the exact same ideas, as you seem to have, like the exact same ideas at the same time I think haha. I was checking out your awesome proposal just yet: https://github.com/sebmarkbage/ecmascript-do-generator But I made these posts this morning: |
The revised proposal is now these semantics for React: $ReactJSXExpression(function*() { return do { ... } })()) function $ReactJSXExpression(generator) {
let yields = [];
let completion = undefined;
let next;
while (next = generator.next()) {
if (next.done) {
completion = next.value;
break;
} else {
yields.push(next.value);
}
}
if (yields.length > 0) {
return yields;
} else {
return completion;
}
} (This is not the code we'd ever actually generate since we could optimize away the generator completely since we know it'll be immediately invoked and added to an array. So it would compile to Note how it either uses the yields or the completion value conditionally. So you can continue just avoiding the It is a little weird that conditionally executing a yield keyword either produces and array or undefined, but that's mostly fine for React since undefined are most equivalent. <div>{for (let item of items) yield item}</div> You could even use yields to explicitly return a value. <div className={if (foo) yield 'bar'} /> However this value would be wrapped in an array. We could potentially unwrap it for single items. Unfortunately that can have consequences for React key semantics, since this would not preserve state when switching between an array of one item vs. two items: <div>
<Sibling />
{
for (let item of items) {
yield <Item key={item.id} item={item} />;
}
}
</div>
Yes. I got excited about this. :) |
To what extent is it possible to special-case single expressions that have no semicolon? I think in my ideal world…
So the completion value basically has no effect. This does require parser magic to treat ASI differently. |
Semi colon is probably a no-go. I think the way you would solve this is to have a linter guarantee that the completion value is always The JS way of solving this is adding a * sigil but it's so ugly. |
Why can we forbid it in the linter but not here? What about in our babel transform? |
I guess it can’t happen in the transform if the tree is the same. |
We could do this at the compiler level where we detect if there is a yield keyword at all in there to switch between generator or completion value. The nice part of doing it at runtime is that it can be expressed as a generic JSX feature without semantic meaning. The semantics can be completely a simple runtime hook. |
Isn't there precedent for this? React already has a special-case for this because of the single-item optimization, i.e. |
@sebmarkbage I think you're missing a bracket in I really like this proposal though. One thing I'm wondering about is the difference in styles for doing conditionals: {
if (1) yield a;
else yield b;
} v.s. {
if (1) a;
else b;
} One would return an array, and the other would return a single element, right? |
Apologies I just made this comment on #39 but it looks like the discussion has moved over here. An additional comment to what's below: I'm worried that in proposing this we're adding complexity to the language in an attempt to compensate for complex code without fully embracing the existing language functionality. That is, by doing this we're making things more complex in the name of simplicity, which is probably the wrong way to go. Original Comment I can't think of a use case that couldn't already be implemented in a better way, predominantly through encapsulation. In simple cases bool expressions are fine (the classic Taking the example from the linked tweet in the original post, we can tidy it up through encapsulation as:
We could go a step further and make Menu itself a function-style component, further simplifying the code. Edit: This is the tweet referred to above: https://twitter.com/vslinko/status/619581969903550464 |
In short when code becomes complex within JSX markup it's best to move the code out of the JSX and replace it with an element, not make it easier to put more code in the JSX markup. We don't want JSX to become the new PHP. ;-) |
@ogwh Over-abstraction is also a serious readability problem, simple problems should be simple to understand at a glance. The current situation is that even a lot of should-be-simple cases become bloated code-wise, this is what the syntax tries to solve. The intended benefits far outweigh the problem of adding another way to shoot yourself in the foot. |
This is my current take on #39
Do expressions in JS are moving quite slowly because it's not very pretty but there seems to be a direction that is sort of shaping up and compromises that occur. JSX users are also the ones that want this the most.
Therefore, I'm proposing that we change up how we do things a bit and instead of pursuing the JS route first, we become an early experiment surface for a more aggressive proposal.
The major rule of this proposal is that we should be backwards compatible with existing JSX for anything but edge cases that nobody would write. That helps us avoid a much larger conversation of #65.
The proposal that all JSX containers are implicit do-expressions with a few tweaks:
If it starts with
{
, then that is anObjectLiteral
, not aBlockStatement
. (Ignoring leading comments and open parenthesis.)The completion value of a
for
,for...in
,for...of
,do
orwhile
loop is a newly created array with the completion value of each iteration in it. If it's acontinue
then the completion isundefined
. The semantics of this doesn't need to be specified in this spec but it needs to have the syntactic mechanisms in the spec to allow implementations and ASTs to be able to do this in a reasonable way.continue
,break
are not allowed at the top scope of these do-expressions. They're allowed in nested blocks but abreak
andcontinue
inside a nested block is not allowed to reference a label outside the inner most do-expression. If you break out of the last statement, that completion value isundefined
.return
is not allowed inside a do-expression other than inside nested functions.throw
expressions are allowed per https://github.com/rbuckton/proposal-throw-expressions but not specified separately.Are there any other tweaks we'd have to do to remain backwards compatible with the expression form?
The text was updated successfully, but these errors were encountered: