»MVC was conceived as a general solution to the problem of users controlling a large and complex data set.«
- Trygve Reenskaug [1]
On an abstract level that sounds very promising!
Introduced the Document Object Model
& JavaScript
Now web pages could be dynamic!
»Path dependence explains how the set of decisions one faces for any given circumstance is limited by the decisions one has made in the past, even though past circumstances may no longer be relevant«
- Dave Praeger [2]
Your mind is like a canyon formed by raindrops of knowledge!
Everything is mutable!
The classic problem of a notification counter being out of sync with the rest of the site was what prompted Facebook to write React
MVC as used on the web uses
bindings, observers and templates
Lots and lots of state!
and state is...
Communication is... everywhere!
We don't have to use exclusively mutable APIs
We can make our communication more straight forward
Which makes it easier to reuse, compose and share that behaviour
UI is our behaviour
One source of truth for the hole application, when any part of that data changes -
we rerender
(data) -> dom
Full render of everything every time at 60 fps
Unfortunatly that would be slow and impractical
(props, state) -> v-dom
(props) -> v-dom
Diff between previous and new v-dom
Wicked fast and very practical
f(old-dom, new-dom) = Δ
Computes minimal changes and puts them in a queue for batching - like a GPU!
»Retained mode is going to be used by at least ten times as many programmers as immediate mode. On the other hand, the world class applications that really step to new levels are going to be done in an immediate mode graphics API.«
- John Carmack [3]
var App = React.createClass({
render: function () {
return React.DOM.h1(null, "Hello, world!");
}
});
var App = React.createClass({
render: function () {
return React.DOM.h1(null, "Hello, world!");
}
});
React.renderComponent(App(), document.getElementById('main'));
var App = React.createClass({
render: function () {
return React.DOM.h1(null, "Hello, " + this.props.name + "!");
}
});
React.renderComponent(App({ name: "Cetrea" }), document.getElementById('main'));
var App = React.createClass({
render: function () {
return React.DOM.h1({ className: "header" }, "Hello, " + this.props.name + "!");
}
});
React.renderComponent(App({ name: "Cetrea" }), document.getElementById('main'));
var App = React.createClass({
onClickHandler: function (event) {
console.log("Clickidyclick");
},
render: function () {
return React.DOM.h1({ className: "header",
onClick: this.onClickHandler },
"Hello, " + this.props.name + "!");
}
});
React.renderComponent(App({ name: "Cetrea" }), document.getElementById('main'));
var App = React.createClass({
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value}); //Merges with current state
},
render: function() {
var value = this.state.value;
return React.DOM.input({ type="text", value=value,
onChange=this.handleChange}, null);
}
});
React.renderComponent(App(), document.getElementById('main'));
var App = React.createClass({
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value}); //Merges with current state
},
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}
});
React.renderComponent(App(), document.getElementById('main'));
var Nav = React.createClass({
render: function() {
var links = this.props.links.map((link) => {
var classes = "link" + window.hash === link.href ? " active" : "";
return <li className={classes}><a href={link.href}>{link.title}</a></li>;
});
return <ul className="links">{ links }</ul>;
}
});
React.renderComponent(Nav({ links: [...] }), document.getElementById('navigation'));
- render
- getInitialState
- getDefaultProps
- propTypes
- mixins
- statics
- displayName
- componentWillMount
- componentWillUnmount
- componentDidMount
- shouldComponentUpdate
- componentWillReceiveProps
Command Query Responsibility Segregation
f(state, event) -> state
f(state, event) -> seq<state>
Immutable stream of read models
Flowing them into React
Fits very well with Event Sourcing
Straight forward communication
Indempotence
Immutability
Makes it trivial to support undo and redo
Makes it trivial to do performance optimisations
Testability
The virtual DOM let's you fake events very easily and you can very easily verify the desired markup would be generated. It's just JS!
Debugability
You can recreate the UI in the exact state that it was in when the user encountered an error
You can easily record how the user is using your application. Simply save the app state in a stream of states and replay that later. Can give you great insight!
Battlehardened on the biggest sites on the web
Khan Accademy
Yahoo & more
Integrates well with existing frameworks since all React cares about is the View.
No reason to not get started on existing projects :)
Gives you immutable vectors, maps and sets
var vec1 = Immutable.Vector(1, 2);
var map1 = Immutable.Map({a:1, b:2, c:3});
var set1 = Immutable.Set(1,2,3);
Vectors
var vect1 = Immutable.Vector(1, 2);
var vect2 = vect1.push(3, 4, 5);
var vect3 = vect2.unshift(0);
var vect4 = vect1.concat(vect2, vect3);
assert(vect1.length === 2);
assert(vect2.length === 5);
assert(vect3.length === 6);
assert(vect4.length === 13);
assert(vect4.get(0) === 1);
Maps
var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
// Map { a: Map { b: Map { c: Vector [ 3, 4, 5 ] } } }
var nested2 = nested.mergeDeep({a:{b:{d:6}}});
// Map { a: Map { b: Map { c: Vector [ 3, 4, 5 ], d: 6 } } }
nested2.getIn(['a', 'b', 'd']);
// 6
var nested3 = nested2.updateIn(['a', 'b', 'd'], value => value + 1);
// Map { a: Map { b: Map { c: Vector [ 3, 4, 5 ], d: 7 } } }
var nested4 = nested3.updateIn(['a', 'b', 'c'], vect => vect.push(6));
// Map { a: Map { b: Map { c: Vector [ 3, 4, 5, 6 ], d: 7 } } }
Structural Equality
var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
assert(map1 !== map2);
assert(Immutable.is(map1, map2) === true);
Cursors
var data = Immutable.fromJS({ a: { b: { c: 1 } } });
var cursor = data.cursor(['a', 'b', 'c'], newData => {
data = newData;
});
// ... elsewhere ...
cursor.deref(); // 1
cursor = cursor.update(x => x + 1);
cursor.deref(); // 2
// ... back to data ...
data.getIn(['a', 'b', 'c']); // 2
Batching Mutations
var vect1 = Immutable.Vector(1,2,3);
var vect2 = vect1.withMutations(function (vect) {
vect.push(4).push(5).push(6);
});
assert(vect1.length === 3);
assert(vect2.length === 6);
Giving people browsing your page on their phone a good experience is important
One measure is time-to-glass
With traditional server side rendered apps it was easier - luckily React components can be rendered server side
What happens when you have loads of client-side JS that renders your UI?
With traditional server side rendered apps it was easy
Luckily React components can be rendered server side
var Header = React.createClass({ render: () => <div>this.props.title</div> });
var Content = React.createClass({ render: () => <div>this.props.content</div> });
var App = React.createClass({ render: () => {
return <div>
<Header title={this.props.data.title} />
<Content content={this.props.data.content} />
</div>;
}});
function reqHandler(req, res) {
fetchDataForRoute(req, (data) => {
res.send("<html><body>" +
React.renderComponentToString(<App data={data} />)) + "</body></html>");
});
}
app.get('/', reqHandler);
[1] Trygve/MVC by Trygve Reenskaug
[2] Our Love Of Sewers: A Lesson in Path Dependence by Dave Praeger
[3] OpenGL and Direct3D by John Carmack