REACT.JS
REMEMBER TO GIVE IT 5 MINUTES
"The faster you react, the less you think."
- Jason Fried
https://signalvnoise.com/posts/3124-give-it-five-minutes
DON'T BE THIS GUY
PRODUCTION
WHAT REACT IS NOT
SJRS / RJS
- Server Javascript Response
- Server responds back with JS that is eval'd
- Ruby Javascript
- Javascript w/ embed Ruby
Reactive-Extensions
RxJS
- JS library by Microsoft
- Observables + LINQ + Schedulers
- Asynchronous data stream handling
More than a template.
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
TEMPLATES ENCOURAGE POOR SEPARATION OF CONCERNS
TEMPLATES SEPARATE TECHNOLOGIES NOT CONCERNS
MARKUP AND DISPLAY LOGIC ARE INEVITABLY TIGHTLY COUPLED
It's not MVC.
SUPER CHARGED RENDERING ENGINE
React is more like a declarative jQuery...
...without the jQuery.
No dependencies.
Just Javascript.
React is a framework for building UIs.
React is a framework for building declarative UIs.
...but why?
// Initialize stuff on load
$(document).ready(function() {
initializeABunchOfThings();
});
// Scattered Events Everywhere
$('#button-1').on('click', function() {
$(this).toggle('active')
});
$('#button-2').on('click', function() {
if ($('#button-1').hasClass('active') {
doSomething();
} else {
doAnotherThing();
}
});
"I need a sane way to build composite stateful interfaces with minimal mental overhead."
- @dan_abramov
Front End, Stampsy
Building a
UI is hard.
Building a UI
Managing state is hard.
State changing
over time is evil.
"Programs that use state in a haphazard way are very difficult to understand. For example, if the state is visible throughout the whole program, then it can be assigned anywhere. The only way to reason is to consider the whole program at once."
Managing complexity is hard.
React helps keep your UI predictable...
...with 2 Silver Bullets.
1. Composition
- Combine simple functions to build complex ones
- Strict Interfaces
2. Idempotence
- Repeatable, with the same result
- Easy to predict output based on inputs
- Immutability gives this to us for free
- Minimize mutating state
ids = [1, 2, 3, 4, 5]
ids.remove(1)
# => [2, 3, 4, 5]
ids.remove(1)
# => [2, 3, 4, 5]
React lets you easily push your data into your UI.
DATA
IN
VIRTUAL DOM
OUT
...tell me more!
BASIC REACT COMPONENT
- STATE
- PROPS
- RENDER
/** @jsx React.DOM */
var HelloMessage = React.createClass({
displayName: 'HelloMessage',
render: function() {
return (
React.DOM.div(null, "Hello ", this.props.name)
);
}
});
React.renderComponent(HelloMessage({
name: "John"
}), document.getElementById('content'));
2. CHILD ELEMENTS
1. NODE PROPERIES
{ id: 'name' }
3. PROPS
4. COMPONENT
HelloMessage Component
/** @jsx React.DOM */
var HelloMessage = React.createClass({
render: function() {
return (
<div>
Hello {this.props.name}
</div>
);
}
});
React.renderComponent(<HelloMessage name="John" />, mountNode);
HelloMessage Component (JSX)
<div id="content">
<div data-reactid=".0">
<span data-reactid=".0.0">Hello </span>
<span data-reactid=".0.1">John</span>
</div>
</div>
HTML OUTPUT
Component Lifecycle Event Hooks
- componentWillMount()
- componentDidMount()
- componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- componentDidUpdate(prevProps, prevState)
- componentWillUnmount()
/** @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.renderComponent(<Timer />, mountNode);
Timer Component
Seconds Elapsed: 9000
getInitialState: function() {
return {
secondsElapsed: 0
};
},
SET THE INITIAL STATE
ON COMPONENT MOUNT
// this.tick();
tick: function() {
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
});
},
DEFINE A FUNCTION
UPDATE THE STATE
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
SET THE TIMER
PASSING THE TICK FUNCTION
ON MOUNT
componentWillUnmount: function() {
clearInterval(this.interval);
},
REMOVE THE TIMER BEFORE
COMPONENT IS UNMOUNTED
render: function() {
return (
<div>
Seconds Elapsed: {this.state.secondsElapsed}
</div>
);
}
DEFINE THE MARKUP IN RENDER
// JSX
React.renderComponent(<Timer />, mountNode);
// JS
React.renderComponent(Timer(null), mountNode);
RENDER THE COMPONENT
/** @jsx React.DOM */
// props:
// currentLat, count, modules
var LiModules = React.createClass({
render: function() {
var moduleNodes = this.props.modules.map(function(module, index) {
return (
<LiModule
id={module.id}
name={module.name}
activities={module.activities}
currentLat={this.props.currentLat}
polling={this.props.polling}
key={index}
/>
);
}.bind(this));
return (
<div className="launchpad-module-groups">
<div className="box instance-group">
<LiModulesHeader count={this.props.count} />
<div className="box-content scrollable instances launchpad-modules">
{moduleNodes}
</div>
</div>
</div>
);
}
});
NESTING
Typical SPA
Architecture
PROPS
UNIDIRECTIONAL FLOW
React Architecture
Components describe the DOM at any point in time.
Upon state change, React will RERENDER.
VIRTUAL DOM
DOM represented in Javascript
SO REPRESENTING THE DOM IN JS... CALL ME MAYBE THIS IS CRAZY.
...wait! Is this the "Shadow DOM"?
No.
Virtual DOM
- The real DOM is SLOW.
- Javascript is actually FAST.
- Server side rendering (Node doesn't need a DOM API like PhantomJS)
- Allow React to provide an easy way to manipulate the DOM and Events
VIRTUAL DOM DIFFING
-
VERY efficient
-
O(n^3) problem into a O(n) one.
-
- No watching or dirty-checking objects
- New DOM tree compared to the old
-
- Only minimal changes to DOM applied
- No unnecessary build/teardown
- Scrolling doesn't break
- Inputs don't break either
REVIEW
- Components describe the DOM at any point in time
- VIRTUAL DOM
- High performance
- less jank, 60fps even in non JIT UI-WebView
- Minimal DOM updates
- Batches reads and writes
- API is very small, (get the gist in like ~30 minutes)
- Doesn't make any assumptions
- use your own datastores
- works with dom libraries/jquery
var BottleGame = React.createClass({
getInitialState: function () {
return {
bottles: 99
};
},
render: function() {
var bottles = this.state.bottles,
warning = (this.state.bottles < 10 ? 'warning' : false);
return (
<div>
<p className={warning}>Bottles: {text}</p>
{bottles > 0 ?
<button onClick={this.handleTakeClick}>Take one down and pass it around</button> :
<button onClick={this.handleAgainClick}>Start again</button>
}
</div>
);
},
handleTakeClick: function () {
this.setState({ bottles: this.state.bottles - 1 });
},
handleAgainClick: function () {
this.setState({ bottles: 99 });
}
});
React.renderComponent(BottleGame(), document.body);
BottleGame
React Component
getInitialState: function () {
return {
bottles: 99
};
},
INITIALIZE STATE
handleTakeClick: function () {
this.setState({ bottles: this.state.bottles - 1 });
},
handleAgainClick: function () {
this.setState({ bottles: 99 });
}
DEFINE HANDLERS
TO UPDATE STATE
render: function() {
var bottles = this.state.bottles,
warning = (this.state.bottles < 10 ? 'warning' : false);
return (
<div>
<p className={warning}>Bottles: {text}</p>
{bottles > 0 ?
<button onClick={this.handleTakeClick}>
Take one down and pass it around
</button> :
<button onClick={this.handleAgainClick}>
Start again
</button>
}
</div>
);
},
RENDER MARKUP
HOW WOULD IT LOOK IN JQUERY OR BACKBONE?
var para = $('#bottles'),
take = $('#take'),
again = $('#again'),
bottles = 99;
function update() {
if (bottles > 0) {
take.show();
again.hide();
} else {
take.hide();
again.show();
}
if (bottles > 1) {
para.text(bottles + ' bottles of beer on the wall');
} else if (bottles === 1) {
para.text('One bottle of beer on the wall');
} else {
para.text('No bottles of beer on the wall');
}
}
take.click(function () {
bottles--;
update();
});
again.click(function () {
bottles = 99;
update()
});
update();
<p id="bottles">9 bottles...</p>
<button id="take">Take one...</button>
<button id="again">Start...</button>
BottleGame jQuery
var BottleGameView = Backbone.View.extend({
el: '#container',
events: {
'click button.take': 'takeBottle',
'click button.again': 'startAgain'
},
initialize: function () {
this.model = new Backbone.Model({ bottles: 99 });
this.render();
this.listenTo(this.model, 'change:bottles', this.handleBottlesChange);
},
handleBottlesChange: function () {
var bottles = this.model.get('bottles');
if (bottles > 0) {
this.$take.show();
this.$again.hide();
} else {
this.$take.hide();
this.$again.show();
}
if (bottles > 1) {
this.$para.text(bottles + ' bottles of beer on the wall');
} else if (bottles === 1) {
this.$para.text('One bottle of beer on the wall');
} else {
this.$para.text('No bottles of beer on the wall');
}
},
render: function () {
var template = _.template($('#bottles_template').html(), this.model.attributes);
this.$el.html(template);
this.$para = this.$el.find('.bottles');
this.$take = this.$el.find('.take');
this.$again = this.$el.find('.again');
this.$again.hide();
},
takeBottle: function () {
this.model.set('bottles', this.model.get('bottles') - 1);
},
startAgain: function () {
this.model.set('bottles', 99);
}
});
new BottleGameView();
BottleGame Backbone
var BottleGame = React.createClass({
getInitialState: function () {
return {
bottles: 99
};
},
render: function() {
var bottles = this.state.bottles,
warning = (this.state.bottles < 10 ? 'warning' : false);
return (
<div>
<p className={warning}>Bottles: {text}</p>
{bottles > 0 ?
<button onClick={this.handleTakeClick}>Take one down and pass it around</button> :
<button onClick={this.handleAgainClick}>Start again</button>
}
</div>
);
},
handleTakeClick: function () {
this.setState({ bottles: this.state.bottles - 1 });
},
handleAgainClick: function () {
this.setState({ bottles: 99 });
}
});
React.renderComponent(BottleGame(), document.body);
BottleGame React Component
POSSIBILITIES
- Not a big commitment
- Plays nice with Backbone, Angular, etc.
- Components can map to PSDs, working w/ designers
- Playback/Repeat/Undo for QA
- Rendering
- SVG, VML, CANVAS
- Web Worker
Other Stuff
- Flux
- Om, Reagent
- ClojureScript,
- Request Animation Frame for DOM updates
CONS
- Its Javascript... so can lead to callbacks on callbacks.
- Render must return a single element
- You have to be explicit.
- Works best with hierarchical data
- Unidirectional
RE'ACTING
THE PORTAL
ACTING
BACKBONE
Usecase
- Hierarchical page
- Modules
- Sub modules
- Modules
- Complexity was rising
- Constant polling
- Backbone rerenders were expensive
NESTED COMPONENTS
REACT COMPONENTS
Thanks!
Special Thanks
- Pete Hunt (@floydophone)
- Murilo Pereira (@mpereira)
- Alex Matchneer (@matchy)
- Dan Abramov (@dan_abramov)
deck
By vestige
deck
- 985