This repository was archived by the owner on Sep 21, 2022. It is now read-only.
Repository navigation
Declarative jsx example #28
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
|
|
||
|
|
||
| // This feature does NOT propose the addition of special templating tags. It only introduces the ability for tags to bind | ||
| // variables, thereby allowing custom components that can be composed in a way that is more readable and feels more declarative. | ||
| // We assume implementations of "push", "if" and "foreach" tags. The actual implementation is quite trivial, and | ||
| // implementable by the end user. | ||
|
|
||
|
|
||
| // Currently, to render a bookstore, you would need the following code... | ||
| var booksData = [{title: "Harry Potter", authors: ["JK Rowling"], ...}, ...]; | ||
| return <div>{booksData.map(function(bookData){ | ||
| var authorElements = []; | ||
| bookData.authors.foreach(function(authorData, idx){ | ||
| authorElements.push(<a href={"http://www.authors.com?id="+author.id} style={{color: 'blue', textDecoration: 'none'}}>{author.name}</a>); | ||
| if(idx !== bookData.authors.length) authorElements.push(", "); | ||
| }); | ||
| return <div> | ||
| <h1>{bookData.title}</h1> | ||
| By: | ||
| { bookData.authors.length === 0 ? <span style={{color: 'gray'}}>Unknown author</span> : {authorElements}} | ||
| </div>; | ||
| })}</div>; | ||
|
|
||
|
|
||
| // The problem is that the code above is complex and hard to follow. it feels a lot like back in the days when | ||
| // user interfaces were built using an imperative API (like IOS, Swing, etc). | ||
| // In order to generate the comma-separated list of authors for each book, we had to create a new array, iterate over | ||
| // the data, and push elements into the new array, and subsequently put the array of elements into the markup. | ||
| // This was a simple example, but you can imagine this widget becoming much more complicated, and it becoming hard | ||
| // to track what's happening in the user interface | ||
|
|
||
| // Now let's try something new. | ||
|
|
||
| // Ideally, you could do the same thing in a less imperative way by evaluating variables lazily... | ||
| <push name="books" value={[{title: "Harry Potter", authors: ["JK Rowling"], ...}, ...]}> | ||
| <div> | ||
| <foreach element="book" collection={books}> | ||
| <div> | ||
| <h1>{title}</h1> | ||
| By: | ||
| <iff test={book.authors.length === 0}> | ||
| <span style={{color: 'gray'}}>Unknown author</span> | ||
| </iff> | ||
| <iff test={book.authors.length > 0}> | ||
| <foreach element="author" index="authorIndex" collection={book.authors} /> | ||
| <a | ||
| href={"http://www.authors.com?id="+author.id} | ||
| style={{color: 'blue', textDecoration: 'none'}}>{author.name} | ||
| </a> | ||
| <iff test={authorIndex < book.authors.length}>, </iff> | ||
| </foreach> | ||
| </iff> | ||
| </div> | ||
| </foreach> | ||
| </div> | ||
| </push> | ||
|
|
||
|
|
||
| // The declarative example is much more readable. And the readability benefits grow super-linearly with component complexity. | ||
|
|
||
|
|
||
|
|
||
| // Now suppose we get a request from our designers to insert a <hr> element between each of our book div tags. | ||
| // With the declarative model, it's really easy. | ||
| <push name="books" value={[{title: "Harry Potter", authors: ["JK Rowling"], ...}, ...]}> | ||
| <div> | ||
| <foreach element="book" index="bookIndex" collection={books}> | ||
| <div> | ||
| <h1>{title}</h1> | ||
| By: | ||
| <iff test={book.authors.length === 0}> | ||
| <span style={{color: 'gray'}}>Unknown author</span> | ||
| </iff> | ||
| <iff test={book.authors.length > 0}> | ||
| <foreach element="author" index="authorIndex" collection={book.authors} /> | ||
| <a | ||
| href={"http://www.authors.com?id="+author.id} | ||
| style={{color: 'blue', textDecoration: 'none'}}>{author.name} | ||
| </a> | ||
| <iff test={authorIndex < book.authors.length}>, </iff> | ||
| </foreach> | ||
| </iff> | ||
| </div> | ||
| <iff test={bookIndex < books.length}> | ||
| <hr /> | ||
| </iff> | ||
| </foreach> | ||
| </div> | ||
| </push> | ||
|
|
||
|
|
||
| // I'll leave it as an exercise for the reader to figure out how to do this with the current imperative-ish code | ||
| // It is an informative exercise, because you realize how tricky it really is once you attempt to modify the code. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
|
|
||
|
|
||
| // I previously claimed that the implementation of "push", "iff", and "foreach" were trivial. | ||
| // We don't need to provide default implementations, but we should allow for them to exist. | ||
| // Here are my implementations, exact syntax of which is to be determined, but you get the idea | ||
|
|
||
| class push { | ||
| render: function() { | ||
| return this.context.push(this.props.name, this.props.value, this.children); | ||
| } | ||
| } | ||
|
|
||
| class iff { | ||
| render: function() { | ||
| return this.props.test === true ? this.props.children : null; | ||
| } | ||
| } | ||
|
|
||
| class foreach { | ||
| render: function() { | ||
| this.props.collection.map(function(element, index) { | ||
| return ( | ||
| <push name={this.props.element} value={element}> | ||
| <push name={this.props.index} value={index}> | ||
| {this.props.children} | ||
| </push> | ||
| </push> | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Alternate syntaxes might look less like "traditional jsx" but could allow additional performance | ||
| // benefits (like short-circuit evaluation for children that will never render). | ||
|
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm interested in how that will be compiled to JS, as a function of
authorargument? How JSX compiler should decide when to compile JSX element as function and when as React element?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of options for compiling JSX to JS, none of them particularly fantastic, but all workable. One would be to compile
author.idto$scope.author.id. Another would be to use thewithoperator.It's worth noting that the expressions will probably need to be wrapped in a function/closure and executed during render instead of during element creation, but that's not a big deal, should be virtually invisible, though there is a slight performance overhead of creating/invoking the anonymous function. This can probably be minimized by checking which variables are in scope and only converting to a function if you are using variables inherited from a tag.
Regardless of the exact desugaring syntax, it's the concept of allowing tags to bind variables that is most important/interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reimplementation of scoping rules is why @iamdustan is saying it's not still "just JavaScript." Of course, anything you write will be "pure javascript" in the sense that it has to compile to JavaScript in the end—but it'll differ in terms of syntax and behavior, which is what adds the cognitive load.
I think the fundamental disconnect here is that your value proposition is that this would make it possible to approximate JS control structures in JSX, but much (most?) of the community doesn't see those control structure elements as something positive. Nobody is under the impression that you're advocating for the inclusion of tempting tags, but if the purpose of your proposal is to enable something they see as harmful, they're going to be against it on the grounds of enabling developers to "fall into the pit of success."