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

Custom attribute namespace #66

Open sebmarkbage opened 7 years ago

sebmarkbage commented 7 years ago

React has two special features in JSX that others might not want. Namely key and ref attributes are treated as special cases. This might go a long way to unifying the semantic meaning as well.

We could namespace such attributes with either a name or symbol. A name might be bloated and a name might be too involved.

<Foo @key="foo" @ref={callback} prop="hi" />
<Foo :key="foo" :ref={callback} prop="hi" />
<Foo #key="foo" #ref={callback} prop="hi" />
<Foo react:key="foo" react:ref={callback} prop="hi" />
jamesknelson commented 7 years ago

Could you also apply namespace symbols to the component itself? Probably using pragmas to define what they do. This way, we could use React.createElement as well as other factories within the same file:

// createRoute(Contact.Details, { id: 5 })
<@Contact.Details id={5} />

The only problem I can see with this is that it is unclear how it would apply to children

<@Contact.Details id={5}>
    {/* Is this a Route, or an Element? */}
    <Contact.Details.PaymentList page={1} />
</@Contact.Details>
chrisregnier commented 7 years ago

I've got another suggestion for syntax that might have a bunch of other uses as well. Annotation blocks? (similar to the new decorator pattern, but I think you want to annotate the component with extra meta information rather than decorate it and return a component) Then anything from the annotation gets passed into the component as a meta or ext prop (kinda like children)

{@ (c)=> { return { key: key, ref:callback}; }}<Component />
// similar to below, except component can be passed to function
<Component meta={(c) => { return {key: key, ref: callback}; }} />

With do syntax on annotation block you could also just return the meta object directly if there's no need to pass in the component {@ { key: key, ref: callback }}

Besides the support for meta information I believe this syntax could also be used for simple conditional blocks. which seems to be another highly requested but frowned upon feature.

The general argument is that writing conditional components isn't as visually appealing as most people would like it, but I definitely don't think adding in any special syntax for any of the different control statements is an acceptable solution. But I think this scenario comes up enough {flag && <Component />}

So how about using the truthy value of an annotation block to determine whether the annotated component should be included or not? In its simplest form it would look like this: {@flag}<Component />

With the meta info: {@ flag && { key: key, ref: callback }} <Component />

Long winded version if you need/support the component as a parameter: {@ (c) => { return flag && { key: key, ref: callback }; }} <Component />

I suppose if you really didn't like the long winded solution then you could allow multiple annotations per component and the component is only used if all are truthy. {@flag}{@{key: key, ref: callback}} <Component />

One benefit to the latter multiple annotations form with direct returns (instead of functions with the component passed in), is the implementing library could short circuit on each annotation block.

diervo commented 7 years ago

@sebmarkbage am I right stating that for any of those suggestions you will have to modify the validation within babel and likely the syntax spec for JSX?

If so, given that today none of that is pluggable in babel, would it be in the future? (augment the grammar or validation in babel particularly for JSX)

Or, should be the goal to keep the grammar restrictive enough so everyone gets forced to play within the same rules without reinventing the wheel or forking the whole thing? (which relates to #65 for some of other breaking changes)

IMHO I think it will be great to have way to semantically differentiate anything that is unrelated with the DOM/HTML spec, so frameworks/transpilers could trivially apply transforms, but yet having a common shared syntax/grammar as a base. That could be useful to standardize things like for example conditionals so frameworks don't need re-invent all that sugar every time in different ways.

gausie commented 5 years ago

Using the # namespace might have some specific implications regarding the private method syntax, but I'm not sure whether that's actually a good or bad thing.

dantman commented 5 years ago

How about using Symbols and supporting ES2015 computed property names (facebook/jsx#108).

<Foo [React.key]="foo" [React.ref]={callback} prop="hi" />

Essentially just desugars to:

React.createElement(Foo, {[React.key]: "foo", [React.ref]: callback, prop: "hi"});
LPGhatguy commented 5 years ago

I worked on a React-like framework at my current company, but targeting Lua and the Roblox DOM instead of JavaScript and the HTML DOM. We hit a similar issue where the namespacing of ref, children, and key didn't sit well with us.

We opted for a solution exactly the same as what @dantman proposed here and in #108. Using symbols as keys has almost the exact same syntax between Lua and JS and we're pretty happy with that route so far!

For example, refs in our documentation: https://roblox.github.io/roact/api-reference/#roactref

jhpratt commented 5 years ago

@sebmarkbage Are there any plans to introduce this syntax? I was looking to create a Babel plugin that uses JSX extensively, and was hoping to use ., ?, and @ (at the minimum) at the start of attribute identifiers. All three are currently used by Lit-HTML as well.

I mention Babel because JSX isn't just for React (though it was created for that purpose), and I recently filed babel/babel#9946 wherein I ask for this to be added to Babel's JSX syntax — they requested I file an issue here and I've run across this (old) issue.