React is an HTML templating language designed for easy rerendering. It can manage view updates automatically by monitoring the state of the original input objects, and updating the original output nodes accordingly.
React templates are expressed entirely in HTML, and can always be validated as such. The directives that control rendering are written in a custom react
attribute, such as <div react="contain myString"></div>
.
OK, here's some sample code that illustrates the basics:
<script>
// this is all the stuff you won't have to do anymore
var updateProfileDiv = function(updatedUserInfo){
var profileDiv = $('#profile');
profileDiv.find('.mugshot img').attr('src', user.mugshotUrl);
profileDiv.find('.mugshot .caption').html(user.mugshotCaption);
if(updatedUserInfo.isAdmin){
profileDiv.find('.admin_links').show();
} else {
profileDiv.find('.admin_links').hide();
}
};
// later...
updateProfileDiv(user);
</script>
<script>
// just once, you anchor a node to its scope
$('#profile').anchor(user);
// Now all your changes to the user object will result in automatic updates to the #profile node
</script>
It's all due to react directives you put in custom HTML attributes in your templates, like this:
<div id="profile">
<img class="mugshot" react="attr 'src' mugshotUrl"/>
<span class="caption" react="contain mugshotCaption"></span>
<a href="/admin_panel" react="showIf isAdmin"/>
</div>
In fact, the output template is equivalent to the the input template, but with appropriate substitutions. In the process, It retains all its original directives that were used to control rendering. Directives are things like 'for' and 'contain' that tell the engine what to do to the template - in some template languages, these are expressed with a custom syntax like {{...}}
or <?...?>
. But since React directives are all contained in custom DOM attributes, they can be preserved after the render step, and considered again during render updates.
Thanks to the previous feature, the library is also capable of managing all view updates, without you manually calling for them. If you use this auto-rerender feature (or 'anchor'), you can delete large swaths of your view code that were previously responsible for keeping DOM output in sync with model state. For example, that would include most jQuery operations, such as $('#mySelector').css({color:'red'}).show()
.
Often in small applications, view management code is minimal enough to go unnoticed. But as the app grows complex, view management becomes exponentially harder, since each subsequent feature has to be respectful of more and more existing features. After all, they share a common DOM tree, and each one can impose new expectations on it. A handshake problem develops as the growing number of features mutate this single resource (see exponential complexity and the handshake problem). In essence, while object orientation allows us to divide and conquer app logic, this single, shared view resource (the DOM) mixes it all together again. Reactive data binding can untie that knot for you.
What sets React apart from other reactive data binding libraries is its emphasis on compatibility and minimal imposition. It is compatible with nearly every other JavaScript library and pattern you might be using.
When rendering templates, React accepts any vanilla JavaScript object, not just a custom class or special data format. Similarly, the input template can be any valid string of HTML or a memory reference to an existing node. The output will always be a DOM node reference, but with appropriate substitutions. (Support for string output mode is planned for later versions, primarily for use on the server-side.)
Since React achieves template substitutions by way of a builder pattern, the browser can always correctly escape your input for the given context. This is an inherent advantage to builder patterns in general. Injection attacks occur when an attacker can define part of a string that will eventually be interpreted by some part of the system as if it were code. With builder patterns, the line between code and input is clear unless you work to blur it, rather than the other way around. Note that React can't prevent all XSS vulnerabilities in your app, but does make the problem much easier to solve.
React doesn't require anything special in your HTML except when you want to tell it what to do at render time, and it never requires non-validating syntax. Since react directives are stored in an unobtrusive custom attribute, they are ignored by all other parts of the browser and your application.
You indicate your substitution values for the update() method by passing it a view object. To handle this, you can define a class, construct a custom input object every time, or even re-use the JS objects already in use by your system.
Unless you specify otherwise in the directives, React won't replace any part of the DOM tree, or even modify it. Many client-side template systems achieve data binding by replacing branches of the tree with freshly-rendered ones every time a change event occurs. This has several undesirable implications:
- Any user interactions that the browser tracks over time (scroll position, cursor highlighting, checked state, etc) get blasted on re-render. You are usually forced to manage more and more of those details over time, as they become important to your app.
- Any modifications you or your helper libraries have made to the DOM tree (think
$('#mySelector').css({color:'red'})
) get inadvertently discarded at render time if you haven't mirrored those changes to the rendering system. - Event handlers must be continuously re-bound to the newly rendered nodes. This is a major source of incompatibility for data binding libraries. Best case, detaching and re-attaching the listeners is slow but achievable. Worst case, proper event rebinding is unachievable without major hacks to one of the two conflicting libraries.
By re-using the existing output tree as the template in subsequent renders, React respects these subtle UI modifications.
React doesn't obscure its directives in the output, so looking at the rendered output with Firebug reveals the same structures you defined in your template file, plus appropriate substitutions. This parity reduces guesswork about which DOM nodes correspond to which parts of your template code.
Manipulating React templates is easy because you don't 'define' them so much as 'provide' them - any node will do. They're just trees, so you can construct and modify them using all the standard manipulation features provided by the browser (appendChild, innerHTML, etc.), even after render. For example, if you move part of a rendered React template from one widget to another, you have just successfully modified both templates. A subsequent update operation on either one will behave as if it's always had the new structure. This is particularly useful if you already have library or application logic that modifies the DOM dynamically. That existing code remains valid, with React's features simply stacking on top in predictable ways.
React aims to minimize unpredictable behavior whenever possible. For example, React won't generate new markup for you. You supply all the control nodes, so there aren't any surprising side-effects on CSS and layout. At every juncture, React emphasizes explicitness over magic.
Since tags themselves are used to delimit directive blocks, there's no risk of misusing template constructs in combination with HTML constructs (for example, closing a tag inside a conditional). Formatting errors in your templates can be detected with standard static analysis tools for validating HTML.
You do make compromises when you choose to express your templates entirely in HTML. For one thing, it's more verbose for some use cases. A mid-paragraph variable substitution, for example, will require an entire tag, rather than just a few curly braces. Furthermore, since the directives are contained in custom attributes, expressing substitutions for attributes themselves can be tricky. This is why React has special-case directives like 'attr', 'class', and 'style', as well as the conditional versions of each.
Some projects you might like to investigate are knockout, jstemplate, angular, which are all fantastic, mature projects. Usually, the main difference between React and these options is the broader expectations they are willing to make on their execution environment - usually resulting in tighter control for the library writer, and less control for the end developer. For example:
- Some are coupled with other components of their own system (such as a custom 'binding' class for each reactive substitution)
- Some lack features features (like dynamic template modification), that are necessary for compatibility with other common use patterns.
- Some rely on a shared global scope for all templates
- Most don't allow re-use of existing JS objects from your system as view inputs
- Most of them are 'too powerful for their own good' - template languages tend to benefit from being as dumb as possible. If, for example, the language allows inlining of arbitrary JavaScript code, and then runs eval() on it, it's likely doing more than it should.
There's also data binding support in both SproutCore and Ext js, but you can't currently use those modules independently, since both are tightly coupled with the rest of their respective application architecture, assumptions, and constraints. If you know of a project I've forgotten, please let me know!
If you're writing a widget that has or will grow to have non-trivial DOM output, you'll get a lot of value from any library that supports reactive data bindings. In particular, React is a good choice if you need minimal design impositions and wide compatibility with other client-side libraries. This is likely to be true when you are introducing reactive bindings to an existing code base little-by-little.
Anyone who dislikes loose coupling, laughter, or puppies. Also, those who need templates that can be rendered by multiple different scripting languages. For such cases, you might prefer something like Mustache, by which much of React was inspired.
Another thing React doesn't handle for you (yet) is animations. If the state of your model changes, and visualizing that change requires an animated transition, you have to handle it yourself, the old-fashioned way. What a drag!
React doesn't support a two-way binding. For example, values the user types into input fields don't get automatically propogated anywhere. This limitation is purposeful, since it's not dead obvious how such a feature should behave. If it should become clear what the expected result would be though, I'll add support for it.
Lastly, React is still a young library, so there are sure to be bugs lurking in dark places. The priority for this library is delivering consistently on existing interface promises, so you can expect bug fixes to come out before feature additions, but check out the todo readme and inline todos for a glimpse of the road map for React.
- Operations that change list size (like slice) do not automatically add or remove output nodes. You still have to call .changed() on the length property of the array.
React currently uses jQuery to facilitate DOM operations, though the dependency is not necessary long term. It also uses some language primitive features from the js.js library.
- Directives start with the name of a React method, followed by space-delimited arguments to the method
- Arguments can be strings (surrounded with quotes) or keys for making lookups in the scope chain. (Scope chains are explained below, in the list of directives).
- Key arguments result in lookups from the scope chain. If lookup for a supplied key fails on the topmost scope, it falls through to the next highest scope in the chain, and so on until it succeeds or exhausts the chain.
- Keys support dot notation (
key.subKey
) for selecting a nested property. - Multiple, comma-delimited directives can be provided in a single
react
attribute. They will be processed sequentially left-to-right.
The easiest way to use React is as a jQuery extension. While using jQuery is not required, it provides a very elegant syntax for your rendering and anchoring operations.
myJqueryNode.anchor(<mixed> data)
Accepts an object or array 'data' to render against myJqueryNode. The substitutions defined in the template node will be taken from the data object, and data bindings will be set up.
myJqueryNode.update()
Updates the target node for its present context. This function is designed for when you create nodes or move them around in the DOM tree, and want them to render correctly for the new context.
Inserts value
as the contents of the node. Value can be a string, number, or DOM node. (note: contained DOM nodes will not be recursed upon in update operations)
Takes the value found at key
in the scope chain, and adds it to the top of the scope chain. Useful for subtemplates, and when dealing with many different properties of a single, nested object. The behavior is identical to that of Mustache's dereferencing feature.
In react, conditionals are achieved purely by way of hiding the nodes through css styling. This way, we don't have to discard the template data, and can easily redisplay it when the input condition changes.
Sets the CSS property display
to none
if condition
is false, and back to the default if it is true
Sets the CSS property visibility
to hidden
if condition
is false, and back to default if it is true
Looping nodes interpret their first two child nodes in a special way. The first child is considered an item template. This will be used as a model to clone each item from. The second node is considered a results container. All the clones will be placed into this node.
Generated item nodes are subsequently evaluated, inheriting their scope chain from the loop node, except each one is extended with it's item-specific bindings or scope change added to the end of the chain.
Note: always place loop directives at the very end of your react attribute
Note: Loops assumes the top-most scope object is an array.
Note: To improve speed and consistency, item nodes are not regenerated on subsequent calls to update()
. The loop()
method will just assume that any node it finds in the results container node was generated by the loop, and will attempt to re-use it.
This is a looping construct that provides a different variable binding for each iteration. Both types of loops (this one and withinEach()
) have the effect of creating a node for each element of the list they traverse. In this version, no new scope is pushed onto the chain, but a binding is created to the current item (and optionally, to the current index), based on your arguments.
Loops over the top-most object in the scope chain, generating a new node for each property. The new nodes inherit scope as normal, but each one gets an its respective item object pushed onto its local scope. In some sense, withinEach can be said to imply a new 'within' directive at the beginning of each item node.
Some things you wish to express in your template will relate directly to tag attributes, rather than whole tags or content blocks. Since all react directives are stored in a single custom attribute, react has a special set of directives to allow you to manipulate other attribute on the node.
Sets checked to active on the current node if condition
is true. Otherwise, sets it to false.
Adds the class as specified by name
to the operative node.
Adds the class specified by name
to the operative node if condition
is true. Otherwise, removes the class.
Sets the attribute specified by attrName
to the specified value.
Sets the attribute specified by attrName
to the specified value if condition
is true. Otherwise, removes the attribute.
Sets the style property specified by attrName
to the specified value.
Sets the style property specified by attrName
to the specified value if condition
is true. Otherwise, removes the style.
Pauses execution with a debugger command at directive evaluation time. Prepend this command to an existing directive that you wish to examine. Example:
if react="contain myString"
was not working as you expect, you can explore react's update routine by changing it to react="debug contain myString"
Works the same as debug(), but only if the condition argument resolves to true.
True, though I believe most are semantically unavoidable, given the constraints and the desired feature set. (I'd would love to hear counterarguments!) See the "What do I give up for these features?" section for a discussion of why they must exist for completeness sake. I don't expect much extension of this list.
Under the hood, react adds string annotations to the input template node, the input view object, and all relevant descendants thereof. To minimize the footprint react has on your objects, the details and object links needed to give these annotations meaning are managed centrally within React. Whenever you indicate to React that a given property has changed on an object, React checks that object for annotations. If it finds any relevant ones, it verifies that the annotations have not been invalidated (through rearrangements in DOM or object structure). It then re-computes any substitutions that depended on those modified properties. I'm writing a more detailed explanation of the internals for a separate document, so please post any specific questions or in the github comments page.
React provides support for a widget inclusion. If the contain directive finds a DOM node as its substitution, it will insert the node as-is.