@garryyao
EF Education First
React is pretty, but my app have already relied on existing libraries
React is all around JSX and Virtual DOM,
but I don't like opinionated thing!
Templates were used exclusively
for view logics, it's hard to rewrite them
User interactions now relies a lot on
DOM manipulation, and
jQuery plugins have been doing good
I use an exiting fetching/store module shipped
with the current MVC framework
React is simply a rendering engine in the clothes of an tremendous opinionated framework
React.render( ReactElement element, DOMElement container, [function callback] )
Let's recall some memories...
insin/DOMBuilder
Provides a convenient, declarative API for generating HTML elements, via objects which contain functions named for the HTML element they create
The DOMBuilder API
with(DOMBuilder.dom) {
var article =
DIV({'class': 'article'}
, H2('Article title')
, P('Paragraph one')
, P('Paragraph two')
)
}
The Desugared JSX
React.DOM.div({className: "article"},
React.DOM.h2(null, "Article title"),
React.DOM.p(null, "Paragraph one"),
React.DOM.p(null, "Paragraph two")
)
Matt-Esch/virtual-dom
A JavaScript DOM model supporting element creation, diff computation and patch operations for efficient re-rendering
// 1: Create a function that declares what the DOM should look like
function render(count) {
return dom('div', {
style: {
lineHeight: (100 + count) + 'px'
}
}, [String(count)]);
}
// 2: Initialise the document
var count = 0; // We need some app data. Here we just store a count.
var tree = render(count); // We need an initial tree
var rootNode = createElement(tree); // Create an initial root DOM node ...
document.body.appendChild(rootNode); // ... and it should be in the document
// 3: Wire up the update logic
setInterval(function () {
count++;
var newTree = render(count);
var patches = diff(tree, newTree);
rootNode = patch(rootNode, patches);
tree = newTree;
}, 1000);
Now that you know React isn't something new, it is a revamped implementation
/** @jsx React.DOM */
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
React.render(<Timer />, document.body);
A mounted React ticker component
<body>
<div data-reactid=".0">
<span data-reactid=".0.0">Seconds Elapsed: </span>
<span data-reactid=".0.1">14</span>
</div>
</body>
React track DOM element by data-reactid
<body>
<div data-reactid=".0">
<span data-reactid=".0.0">Seconds Elapsed: </span>
<span data-reactid=".0.1">14</span>
</div>
</body>
React only update the DOM node that is binded to an expression value
React.createElement("div",
null, // DOM attributes is static
"Seconds Elapsed: ", // first text node child is static
this.state.secondsElapsed // second text node child is an expression
)
first text node child of .0.1 (14) is the only child that React will touch in each render cycle
<body>
<div data-reactid=".0">
<span data-reactid=".0.0">Seconds Elapsed: </span>
<span data-reactid=".0.1">14</span>
</div>
</body>
You can make any DOM change you like, as soon as the element with ".0.1" is still online
<body>
<div data-reactid=".0">
<span data-reactid=".0.0">Seconds Elapsed: </span>
<span data-reactid=".0.1">14</span>
</div>
</body>
componentDidMount is invoked once immediately after the initial rendering occurs. At this point in the lifecycle, the component has a DOM representation which you can access via React.findDOMNode(this).
componentDidUpdate is the invoked each time after the component's updates are flushed to the DOM. (This method is not called for the initial render)
more lifecycle events...
https://facebook.github.io/react/docs/component-specs.html
Init your jQuery plugin on componentDidMount
Update your jQuery plugin on componentDidUpdate
Query your data store or send ajax requests on componentDidMount
Add in JS/CSS animation on
componentWill(Did)Update
Trigger custom change events of your data models on
componentWillReceiveProps
Teardown your view/controller object on
componentWillUnmount
That's all great stuff, but what if I already have view logic written in handlebars templates?
You just need to adapt $.fn.html to React.render that gives you reactively rendering
Handlebars -> HTML -> .html()
Handlebars -> HTML -> $el.html()
Handlebars -> HTML -> Virtual DOM
React.render(React.dom.div(...), $el.get(0), callback)
So what exactly can be rendered by React?
The primary type in React used to present DOM element, now support all HTML5 and SVG element
var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
React.render(root, document.getElementById('example'));
The markup you created in JSX will be compiled as ReactElement
var root = <ul className="my-list">
<li>Text Content</li>
</ul>;
React.render(root, document.getElementById('example'));
It shall be either one of the following types:
The Custom UI component in React
But I already have my own widgets system, React Component cannot co-exist with my components
Case Study: TroopJS + React
Widget.extend({
"sig/start": function() {
this.num = 0;
this.query('/api').then(function(data) {
return this.signal("render", data);
})
},
"sig/render": function(data) {
var tpl = '...';
return this.html(tpl(data));
},
"dom:button/click": function() {
var changes = '...';
this.command('/api', changes);
}
});
TroopJS widget APIs
Our ideal widget
What about a hybrid widget
We end up with combining the goodness from both worlds...
Widget.extend({
"sig/start": function() {
this.num = 0;
this.query('/api').then(function(data) {
this.setState(data);
}.bind(this));
},
render: function() {
return (
<div className="awesome">
<p>{this.state.num}</p>
</div>
);
},
"dom:button/click": function() {
var changes = '...';
this.command('/api', changes).then(function(newData) {
this.setState(newData);
}.bind(this));
},
componentDidMount: function() { /*...*/ },
componentDidUpdate: function() { /*...*/ },
componentWillReceiveProps: function() { /*...*/ },
componentDidUpdate: function() { /*...*/ },
});