Component-based Web Apps with React
ReactJS is a toolkit for building component-based web applications. React shines at being fast and effortless by its clever use of DOM simulation to minimize the amount of DOM manipulations and look-ups performed. React components are written in a blend of JavaScript and XML, but are compiled into pure Javascript using React’s compiler tools. Here is an example of a component that displays the current time and pops up an alert when you click on it:
<script type="text/jsx">
/** @jsx React.DOM */
var Clock = React.createClass({
render: function() {
return (
<div onClick={this.sayHi}>
The time is: {this.props.when.toLocaleString()}
</div>
);
},
sayHi: function() {
alert("Hello");
}
});
React.renderComponent(
<Clock when={new Date()} />,
document.body
);
</script>
Since the code blends XML and JavaScript, the syntax cannot be directly executed by the browser. Which is why the script tag needs the type set to text/jsx
. To run it, the code needs to be compiled into pure JavaScript with React’s compiler tools. Alternatively, the website can include the JSX real-time compiler (another JavaScript library), though this is typically reserved for development or testing purposes only.
The React compiler also requires a comment line be placed at the top saying @jsx React.DOM
. This line tells the React compiler that the embedded code needs to be compiled with the React.DOM
parameter. This is subject to change in the future, but holds for React v0.10 RC.
After you compile the code, the XML will be transformed, and the following JavaScript code will be generated. As you can see, the XML is merely syntactic sugar that allows the interface to be written in HTML syntax.
<script type="text/javascript">
var Clock = React.createClass({
render: function() {
return React.DOM.div(
{onClick: this.sayHi},
"The time is: ",
this.props.when.toLocaleString()
);
},
sayHi: function() {
alert("Hello");
}
});
React.renderComponent(
Clock({when: new Date()}),
document.body
);
</script>
Using XML
Every React component is simply an object with a render()
function. This function returns XML which describes what the interface looks. It is important to remember that the XML is not a direct mapping to the HTML that we are accustomed to. When you write <table><tr><td></td></tr></table>
, you are not creating a table. In reality, you are creating three components (table
, tr
, and td
), and passing one component as the parameter to another.
This also means that not every attribute you set in the XML will appear in the resulting HTML. The component must specifically handle that attribute for it to be used. Fortunately, React’s default set of components support all the common attributes typically used, such as id
, href
, src
, type
, checked
, etc.
One deviation from the norm is that all attributes must be camelCased. For example, <input onclick="" />
is written as <input onClick="" />
, and <td colspan="3">
becomes <td colSpan="3">
. Also, the style
attribute is given special treatment in that it expects a hash object of styles as the parameter instead of the usual CSS syntax. An example style
attribute would be <div style={ {fontFamily:"Arial", marginLeft:10} }></div>
.
Another trait of XML is that its syntax is stricter than HTML. All XML tags must be accompanied with a closing tag (</td>
, </p>
) or be self-closing (<input />
).
Since XML is just used as a method to call components, all custom components are called in the same way.
<!-- inside the render function -->
<table>
<tr>
<td>
<ShoppingCart size="mini">
<List />
</ShoppingCart>
</td>
</tr>
</table>
The name of the component is simply the variable name that you assigned to it at creation:
var ShoppingCart = React.createClass({ /* ... */ });
You may have noticed curly brackets inside the XML. These contain JavaScript expressions that will be copied verbatim when the code is compiled into JavaScript.
The Role of Components
The most important aspect of a component is the render()
function. This function does not render HTML, nor does it generate a DOM node to append to the webpage. Its role is to generate a tree of JavaScript objects that resemble how the DOM is supposed to look; a sort of “simulated DOM”, if you will. But, it’s all done with JavaScript objects which are lightweight and easily garbage collectable.
The simulated DOM cannot be used directly. Instead, it is passed to React, which uses a difference algorithm to detect changes from the last simulated DOM. These differences are then applied as a series of update operations to the actual webpage.
Other than render()
, components also steer event registration and expose life-cycle events so that developers can perform additional tasks when components are created and destroyed.
DOM Simulation
Since React relies on the difference algorithm to detect changes and updates, it is no longer necessary to write code to modify the DOM. This means you no longer need to call setAttribute()
or input.value
. All of this is handled invisibly by the React engine.
All that your component needs to do is supply a render()
function that builds up the simulated DOM. Every time the page needs to be updated, render()
is called, and a new simulated DOM will be generated. This means less code to write and maintain.
The reason this is possible is because the simulated DOM executes quickly, allowing React to minimize the performance hit of having to regenerate the whole tree with each render. React is also able to use several heuristics to make the O(n^3) tree traversal problem closer to a O(n) problem.
Event Handling
Event handlers are attached to components using attributes such as onClick
onMouseOver
, onKeyPress
, etc. These event handlers only work on HTML tags and not on custom components. For custom components, you must pass the attribute to one of the HTML tags within the custom component. An example containing event handlers is shown below.
<!-- inside the render function -->
<div>
<button onClick={this.actionA} />
<button onClick={this.actionB} />
</div>
Behind the scenes, React attaches the event listener to the base node and uses event delegation to propagate the event to the target component. This is done for performance purposes, so you can safely add events to each row of a list.
The same technique can also be used to pass callback functions to components, as a way for components to communicate with their parent.
var MainApp = React.createClass({
render: function() {
return (
<div>
<ShoppingCart onCheckout={this.checkoutCart} onEmpty={this.emptyCart} />
</div>
);
},
checkoutCart: function() { /* ... */ },
emptyCart: function() { /* ... */ }
});
Attributes
Data is passed to components using attributes:
var myData = {list: [], amount: 0, taxes:1.15};
var MainApp = React.createClass({
render: function() {
return <ShoppingCart goods={myData} />;
}
});
var ShoppingCart = React.createClass({
render: function() {
return <div>Amount: {this.props.goods.amount}</div>;
}
});
The component then retrieves the data by accessing it from the this.props
property. Unlike traditional HTML where attribute values are strings, React attributes can be assigned complex objects, because after the code is compiled, it is all converted to JavaScript objects.
Small components typically have their data passed in through attributes, while large components (behaving like a full-fledged application) retrieve their data from external sources and divide up and pass the data down to the smaller components.
Large components can store data internally in the form of state
. States can be seen as data stores that are private to a component. The data in states is set by calling setState(objectHash)
on the component. This makes the data available from the this.state
property. Calling setState()
triggers a component update, which invokes render()
. The usage of state
and props
is similar, but is semantically different for the convenience of component implementers.
The Robustness of Attributes
You may have noticed that React relies a lot on attributes. Unlike other toolkits which bring a lot of different contraptions to the table, React doesn’t really pack that much. Which is why, unless you are bringing in other libraries that have data persistence or a universal messaging system, you will have to rely on attributes to pass functions and objects around. This isn’t necessarily a bad thing. The simplicity of React makes it quite easy to grasp and use. The attribute system is quite resourceful and imposes a strict top-down approach to passing data.
Making it Scalable
So far we’ve seen that React can update the interface however often it wants, even for minute changes to the data, because React will calculate the minimal set of changes needed to update the DOM and therefore be quite efficient at it. However, along the way you may encounter performance issues, or simply want to optimize your components. The secret to optimization is in the shouldComponentUpdate()
function that is called before render()
. Each component has this function that can override whether a particular component and its children are updated. This function takes the new props
and state
as arguments. You can use these to check whether the update is really necessary.
For example, if a particular data list has a timestamp, implementations can simply compare the timestamp with the old timestamp and save the interface from having to update that particular component.
For a more contrived example, the entire application can be created based on checking old values against new values. Usually when new data is available from the server, a data model library such as Backbone needs to identify which specific property was changed and the corresponding handlers for that property needs to be triggered. In this approach, when new data is available, it immediately replaces the old data, and the interface is updated. As the update is propagating up the tree, each component only needs to check whether the new data is different from the old data in the shouldComponentUpdate()
call, to determine whether that branch of the tree needs to be updated.
Another point to keep in mind when trying to improve performance, is that the render()
function may be called many times, and it is best to keep heavy calculations (such as sorting) outside this function and possibly cache the result. Small calculations, such as subtotals are fine when performed inside render()
.
Conclusion
Because React only comes with tools to build the user interface, it doesn’t have tools to structure your data or structure your service providers. People have been successful in using React as the UI interface and using Angular as the framework. Others are able to make it work using React and nothing else. There is no prescription of what method is best. Many combinations work, which goes to show the flexibility that React is able to afford. You will just have to find your own combination. Check out the project’s page, and let us know how React works for you.