A Review of the react.js Framework
2015-02-16 - By Robert Elder
Updated Nov 11, 2016: Added image of demo app.
I recently tried out Facebook's new react.js framework v0.12.2 by working on a small simulated stock ticker demo app. My overall impression of the framework is favourable, but I still found a few things to pick at, and I'll discuss these below. Here is an image of the stock ticker demo app:
First, I'll discuss what I think this framework gets right, and the use cases where it can really shine. In particular, react.js seems to solve a problem I became aware of a few years ago. Back then, I had realized that the trend for web development was going toward highly interactive DOM manipulation that usually resulted in markup on the client that was very different from what was sent from the server. Being a man of extremes, I decided to experiment with building UIs that were 100% made in javascript, and were inserted into the DOM manually (with jQuery) as the user interaction required. My idea was that I might be able to build very complex UI components (like blog layouts, or submission forms) and pack them into javascript libraries. Then I could 'build' a whole site just by including a couple of javascript libraries, and putting an empty <body> tag on the home page. The results of my 100% javascript experiment were not positive for the following reasons:
- Incrementally building the markup for deeply nested UI components requires that you consider and track the dependency graph of your UI components.
- You must take care not to introduce circular dependencies in whatever rendering method you use.
- Modularity becomes very difficult to maintain as the size of the app scales.
The above problems are where I believe react.js fits in as a good solution.
Good Points
React.js allows you to describe your UI in a modular way that captures the dependency tree of your view. React makes it particularly easy to build very asymmetric DOM structures in pure Javascript. It does this by allowing you to clearly describe a one-to-one mapping between the HTML elements you plan to put in the DOM tree and the context free grammar of the Javascript functions used to represent these elements. This method of describing the DOM also implicitly captures the dependency tree of your UI components, so when something changes, the framework is able to traverse the tree and figure out what changed. For example, here is a comparison between the HTML and the equivalent react.js version:
var Foo = React.createClass({displayName: "Foo",
render: function() {
return (
React.createElement("tr", null,
React.createElement("td", {className: "foo-class"},
React.createElement("span", null, this.props.variableFromParent)
)
)
);
}
});
React.render(
React.createElement("table", null, React.createElement(Foo, {variableFromParent: "somevalue"}))
document.getElementById('content')
);
results in
<someelement id="content">
<table>
<tr>
<td>
<span>somevalue</span>
</td>
</tr>
</table>
</someelement>
In jQuery if you wanted to insert some variable tr content into a table, you'd probably put a class or id selector on the table, then use something like $('table.classname').append(...) to insert it. This will work fine until you find out that the content isn't inserted because the table tag doesn't exist on the page yet. The jQuery selector is a method of directly addressing the DOM tree that ignores the dependencies that are typically so important in deeply nested UI components.
React also has a sensible way to manage information flow via the 'this.props' and 'this.state' variables. In most cases, elements that are deeper in the DOM tree depend on information that comes form a larger set of information that is passed down from the root node. This is done by passing variables down on components, and then accessing this information inside children using 'this.props.<variablename>'.
var Foo = React.createClass({displayName: "Foo",
render: function() { return (React.createElement("span", null, this.props.variableFromParent)); }
});
...
React.createElement("div", null, React.createElement(Foo, {variableFromParent: "somevalue"}))
...
Occasionally, the rendering of a parent node will depend on, or be affected by changes in a child node. To deal with this case, you can pass a callback into the child node, then access it in the child node via the 'this.props' variable. When the changes happen in the child node, you trigger the callback function which can use the 'setState' method in the parent. This will trigger the parent to re-render, so you can now inspect the 'this.state' variable to see what changed.
var ChildComponent = React.createClass({displayName: "ChildComponent",
render: function () {
return (React.createElement("div", {onMouseOut: this.props.onSomeEvent}));
}
});
var FOO = React.createClass({displayName: "FOO",
getInitialState: function() {
return {somevariable: undefined};
},
onSomeEvent(newvalue){
this.setState({somevariable: newvalue});
},
render: function() {
if(this.state.somevariable == 1234) /* Do something different */;
return (React.createElement(ChildComponent, {current_state: this.state.somevariable, onSomeEvent: this.onSomeEvent}));
}
});
Finally, my last point was about modularity. Because of the incremental nature of most web development, you'll start off with something like a simple span that contains plain text. But as the needs of your application grow, you realize that you need to add more and more features and child elements to this span, until it actually starts looking like a full UI component itself. This is where react allows you to easily modularize your growing UI by swapping out literal HTML elements for a component based ones. Typically, when you need to add a new button or piece of text to your UI, you need to add it to the leaf nodes of your application. In react, this is the easiest place to add or change things. If you were using jQuery DOM insertions, you would have to think more possible interactions with parent nodes in the DOM tree.
Efficiency
In traditional MVC the idea of re-rendering the entire view for every small change is considered the anti-pattern, but this exactly what react.js does. React is in fact a bit slower than MVC/KVO, but there are some convincing arguments that suggest react does have some performance advantages when it comes to memory footprint. The argument centers around that idea that as an application grows the asymptotic memory consumption of React will be smaller than in the traditional MVC/KVO approach.
Bad Points
React has an optional domain-specific language called JSX, and I believe it is a step in the wrong direction. It seems like a rite of passage that every new framework needs to come with its own domain-specific language. React's JSX language is intended to make it possible to write HTML directly in javascript, since the equivalent javascript functions resemble the structure of HTML anyway. In React's case, I don't think JSX adds much, and it only takes away from being able to understand what is actually going on. When I think about people out there who are trying to start learning about web development today, I realize that modern web development has blended together so many different languages with different syntactic and semantic rules that we now have an almost incomprehensible mess. If you're using React's JSX, you'll see a weird pseudo-HTML mixed up with javascript, where the attributes on HTML nodes are not what you'd expect. They are in fact the javascript functions to access their DOM API equivalent. You'll get the same surprise if you expect to be able to set inline CSS style attributes the way you always have in the past. I can see the advantage to being able to just write HTML naturally into javascript, but this is like learning a new dialect of HTML.
As an example, the following does not work in JSX:
<div class="highlighted"></div>
since you have to use 'className' instead of 'class'
<div className="highlighted"></div>
What is an even worse infraction, is that the framework chooses to not output 'class' as an attribute! In the case of some common errors like using 'class' instead of 'className', you do get a warning in the console log, which is helpful. If you happen to misspell the attribute, the framework will silently choose not output it without providing any errors or warnings anywhere. This behaviour is partially documented on their Gotchas page:
"If you pass properties to native HTML elements that do not exist in the HTML specification, React will not render them."
These differences with DOM properties and attributes are further discussed on this page.
React's JSX uses confusing syntactic overloading of 'attributes'. In JSX you'll write a lot of 'attribute={...}' on both component tags, and JSX HTML tags, but the meaning of these attributes will be different. Let's use an example:
/* This will only alert for 'Click on div.'*/
var Foo = React.createClass({
render: function() {
return (<div onClick={function () {alert('Click on div.');}.bind(this)}>Some text.</div>);
}
});
React.render(
<Foo data={ "title1" } onClick={function () {alert('Click on Foo.');}}/>,
document.getElementById('content')
);
The meaning of 'onClick=' on the Foo component is to make 'Click on Foo' function available as a variable inside the Foo class. For the 'onClick=' on the div tag, it means to set the onClick listener on our div to display 'Click on div' when the user clicks our div. Originally, I had mistakenly believed that I would get both alerts. If you want to received both alerts you'd do:
/* This will display both alerts */
var Foo = React.createClass({
render: function() {
return (<div onClick={function () {this.props.onClick(); alert('Click on div.');}.bind(this)}>Some text.</div>);
}
});
React.render(
<Foo data={ "title1" } onClick={function () {alert('Click on Foo.');}}/>,
document.getElementById('content')
);
The name 'JSX' is already taken for another javascript extension that is completely unrelated to react.js. At least, I think this is true. I haven't been able to find a reference stating conclusively either way yet. If you Google for 'JSX', the first result is currently JSX, a faster, safer, easier JavaScript. This project appears to be a compiler tool that compiles a statically typed language to javascript. This project also looks like it pre-dates react.js by at least a year. Did nobody at Facebook think to Google 'JSX' before naming this language extension?
Conclusion
In conclusion, I believe that react.js's paradigm of having a close relationship between the DOM structure and the javascript used to describe it is one that will catch on. I have a few nitpicks with react.js, but most of them are with the JSX language extension. The complaint I had about the confusing attribute names could also be applied to the non-JSX javascript way of describing elements, because that's where these names come from. There isn't really much to pick at with the framework itself, because it doesn't really do much (which is a good thing). People have been taught that anything other than MVC is blasphemy, but MVC isn't easily applied when your UI components are made up of a lot of nested independent widgets that need to communicate. React is certainly not a replacement for jQuery and a testament to this is the fact that Facebook's tutorial code for React depends on jQuery. It may replace some of the existing MVCish javascript frameworks, but I see it more likely being used in addition to what already exists.
Amazon Cloud Servers For Beginners: Console VS Command-Line
Published 2017-03-20 |
$1.00 CAD |
A Technical Review Of Adding Support For Google's AMP Pages
Published 2016-11-22 |
Learning Laravel and angular.js
Published 2015-02-06 |
A Weird Old Tip About EC2 Instances
Published 2015-02-07 |
D3 Visualization Framework
Published 2015-04-21 |
Silently Corrupting an Eclipse Workspace: The Ultimate Prank
Published 2017-03-23 |
XKCD's StackSort Implemented In A Vim Regex
Published 2016-03-17 |
Join My Mailing List Privacy Policy |
Why Bother Subscribing?
|