facebook / jsx

The JSX specification is a XML-like syntax extension to ECMAScript.
http://facebook.github.io/jsx/
1.96k stars 133 forks source link

Alternative syntax for passing JSX content to props #62

Closed danielearwicker closed 7 years ago

danielearwicker commented 7 years ago

Silly (but succinct) example:

const If = ({test, yes, no}) => test ? yes : no;

Today we can say this:

const eg1 = (
    <If test={allowed}
        yes={
            <button>Launch missiles</button>
        }
        no={
            <span>Access denied</span>
        } />
);

Proposal is to support this alternative, inspired by XAML:

const eg2 = (
    <If test={allowed}>
        <If.yes>
            <button>Launch missiles</button>
        </If.yes>
        <If.no>
            <span>Access denied</span>
        </If.no>
    </If>
)

In short, if an element name contains . and the first part is the parent element's name, the second part is a prop name and the content becomes the value passed to that prop. Nothing else changes - it's merely an alternative way to specify the prop value.

It remains consistent with XML, where . is allowed; indeed, XAML was forced to do this in order to be an XML dialect and yet be flexible about how properties are specified on objects. JSX doesn't have that challenge, nevertheless in more complex examples it would lessen brain- and eye-strain to keep the structure of the tree obvious by staying in JSX syntax instead of { ... } blocks stretching across many lines, and with a few layers of nesting alternating syntaxes.

And a further enhancement, say we have:

const For = ({items, each}) => <div>{items.map(each)} </div>

Today we can say this:

const eg3 = (
    <ul>
        <For items={things} 
            each={item => (
                <li>{item}</li>
            )} />
    </ul>
);

But how about:

const eg4 = (
    <ul>
        <For items={things}>
            <For.each item>
                <li>{item}</li>
            </For.each>
        </For>
    </ul>
);

That is, in these "prop elements", which have no other purpose for their attributes, they can optionally specify valueless attributes. These become the parameter names of a function, and that function is then is passed to the prop. (Once again, it maps exactly to the previous example.) If no attributes are specified, as in eg2, then there is nothing variable for the content to depend on and hence it can just be a simple value rather than a function returning a value.

I found a prior issue https://github.com/facebook/react/issues/848 that seemed to be asking for something similar but wasn't as precisely mapped to existing concepts. Here I'm not talking about anything that changes the model, just a way of staying in JSX and reducing the mental overload of switching syntaxes when writing/reading trees.

sophiebits commented 7 years ago

Your eg2 is already possible in React if you write the If component cleverly.

Your eg4 example would need to change the semantics of JSX pretty drastically because currently every subexpression is eagerly evaluated and there is no way to represent this currently in JSX. I think @jimfb had a proposal somewhere (which we're unlikely to take) but I can't find it now.

danielearwicker commented 7 years ago

Yes, @zpao told me how a component could dig down into grandchildren. Sounds scary and wrong!

Regarding eager evaluation, that means I should correct this:

If no attributes are specified, as in eg2, then there is nothing variable for the content to depend on and hence it can just be a simple value rather than a function returning a value.

Instead, my If example should be:

const If = ({test, yes, no}) => test ? yes() : no();

i.e. yes and no are functions, and so eg2 should generate nullary functions, being equivalent to:

const eg1 = (
    <If test={allowed}
        yes={() => <button>Launch missiles</button>}
        no={() => <span>Access denied</span>} 
    />
);

i.e. what I've specified here is purely syntactic sugar, a first-pass transformation based on a pattern that can be found in element names. It would "compile down" to combinations of JSX and {(...) => ... }-bracketed chunks, with behaviour exactly as supported today, as in the above examples.

sophiebits commented 7 years ago

What you have is already valid syntax though (which means something different than the compilation you suggest). I don't think we're likely to support something like this in JSX itself but you should feel free to make your own syntax extension if you think it's valuable. Thanks for sending the idea!

danielearwicker commented 7 years ago

Ah, I see . is already supported and just passed through. : would be a possibility though.

Really I'd like to see this in TypeScript, and it's very unlikely to be adopted there unilaterally.