Upload
geoff-harcourt
View
253
Download
2
Embed Size (px)
Citation preview
How we use React.jsA case study in completely re-implementing your UI in one month
Iron Yard Crystal City - Tuesday April 7, 2015
Hi, I’m Geoff• CTO and Cofounder at Cortex
• Began career as an analyst and project
manager
• Began development as a hobby
(interested in baseball stats)
• Fell in love with Ruby after initial
flirtation with PHP
• Started Five Tool Development, a web
development agency
What does Cortex do?
• Our application provides commercial building operators and
engineers with minute-to-minute recommendations about energy use
and live data from hundreds of sensors and meters
• Our first building saved half a million dollars in energy costs during
their first year using Cortex
• 95% of our customer use is through mobile apps. We deliver a
responsive web application in a WebView container to iOS and
Android
What we’re going to talk about
• Super-fast overview of what React is
• How we used React to dramatically improve Cortex
• Even faster overview of Flux
• Questions (feel free to stop me any time)
Problem
Building engineers frequently work in spaces with no network
connectivity.
They expect to be able to get their data everywhere.
Complication
Need to support three platforms (web, iOS, Android) with limited
resources.
We have already invested hundreds of hours in a first-class HTML-
based interface.
The Plan
Turn our webapp into a persistently-open single-page application
(SPA). Users will be on the same page all the time, so the application
will never reload in the browser.
Our application will fetch and cache data when the network is available,
and present it even when the network is unavailable.
What is MVC?
Source: Google (https://developer.chrome.com/apps/app_frameworks)
Lots of JavaScript MVC Frameworks
Compared to Ruby, JavaScript is the
Wild (Wild) West.
Space is less mature, but filled with really
interesting new innovations and ideas.
Cutting-edge solutions, but not a lot of
consensus around which are the best yet.
Source: James T. West
Backbone
• Created by Jeremy Ashkenas from the New
York Times visualizations group
(he built Coffeescript, Underscore.js)
• Early pioneer in JavaScript MVC frameworks
• Focus on data sync between browser and
server-based API, leaves view/presentation
layer up to developer
Ember• Created by Yehuda Katz (of Rails- and jQuery-core fame) and Tom Dale
• Uses two-way data binding
• Very opinionated, does things “the Ember Way”
• Explicitly for single-page apps
• Has awesome routing
• Liked by many Rails developers
Angular• Developed at Google
• Uses two-way data binding
• Less opinionated than Ember, which means it
is more flexible, but also requires you to do
more heavy lifting yourself
• Not just for SPAs
• Extends HTML with custom attributes
React• Developed at Facebook
• Unidirectional flow of data (one-way binding)
• Re-renders everything when something
changes, uses a “virtual DOM” to calculate
differences
• Can be implemented in small pieces, does not
require full-scale use across application
• Younger than previous options, rapidly growing
With or without you
(by “you”, I mean a network)
• Cache data in browser using
localStorage API
• Always be on the same page,
never reload the page due to user
clicks
• Give friendly messages when
network is unavailableSource: The Edge
The need for speed
• Context changes should be snappy
• Pre-fetch data the user will likely
want and put it into localStorage
before the user requests it
• Minimize delays in rendering UI
components due to cascading
changes with asynchronous
execution Source: Miramar Naval Air Station, aka “Top Gun”
So how does React work?
• React’s building blocks are Components
• Components are JavaScript objects tasked with outputting HTML
• Only requirement is a render() function that returns HTML
• Components can maintain their own internal state
A dumb component
<div class=“commentBox”>
Hello, World! I am a CommentBox.
</div>
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
React.createElement(CommentBox,{});
A slightly less dumb component
<div class=“greeting”>
Welcome, Homer
</div>
var Greeting = React.createClass({
render: function() {
return (
<div className="greeting">
Welcome, { this.props.username }
</div>
);
}
});
React.createElement(
Greeting,
{ username: “Homer” }
);
A slightly smart componentvar UserList = React.createComponent({
getInitialState: function() {
return { users: [] };
},
users: function() {
return _.map(
this.state.users,
function(user) {
return (
<li className="user">{ user.name }</li>
);
},
)
},
render: function() {
return (
<ul className="users">
{ this.users() }
</ul>
);
}
});
# with no users (at initialization)
<ul class=“users”></ul>
# with one user (state changed)
<ul class=“users”>
<li class=“user”>Geoff</li>
</ul>
# with two more users and one removed
<ul class=“users”>
<li class=“user”>James</li>
<li class=“user”>Su</li>
</ul>
Smarter is not better
• Props are immutable, easier to track
• Favor props over state whenever possible
• Make component functions as dead-simple as you can
• Dumber components are easier to test, and likely faster to render
Components nest
• Any complicated element or repeated set of elements should be its
own component
• When the parent re-renders, React figures out whether or not the
children should remain, be updated, or be removed
• Components can be passed in as props to a component or returned
from a function in a component
• React can render an array of components
React lifecycle• React offers a number of “hooks” that allow you to trigger events at
certain points in the Component’s lifecycle
• Examples of lifecycle events: when a component renders, when it gets
updated state, when it is removed from the DOM
• Uses: start a timer, listen for UI events, tie in jQuery or other libraries
after a component is rendered, then remove those timers/listeners when
the component is unmounted
• Cortex uses lifecycle to trigger chart drawing with d3 and updates every
30 seconds
Use functions liberally
• Functions are fast (mostly)
• Keep logic in functions outside of render().
render() should look as much like HTML as
possible.
• Just like your Ruby code, many short
methods with limited arguments makes for
cleaner, easier to test code than one
massive God method that does all the
decision-making Source: Arkansas
Shared behavior
• React components share behavior through Mixins
• Mixins don’t provide the same inheritance or composition capabilities
as Ruby (no overriding methods or implementations)
• Example of shared behavior: all cards that collapse or expand have
the same collapse/expand mixin
Lots of components!Page ->
Dashboard ->
Navigation ->
Navigation Item(s)
Card Collection ->
FlashMessage(s)
Card(s) ->
Card Header
Card Chart
Card Tab
React tips
• Don’t manipulate HTML yourself!
React can’t keep track of HTML changes you make after rendering. If
you’re tempted to use jQuery or another library to alter HTML, figure
out a way to do it through the component’s props and state.
• Don’t be afraid to break components into smaller sub-components
More React tips
• Components can communicate with their parents through functions.
Pass a function from the parent to the child as a prop, then the child
can call it and send a message to the parent. Super useful, but don’t
overdo it!
• Start small. One of the best things about React is that you can
implement it in little pieces in your UI as you find opportunities to use
it.
What about global state?
• In our first attempt at using React, we had a top-level “Page”
component that rendered all the various views inside of itself. The
Page was responsible for maintaining global state such as the date
being viewed, the current building, and the desired page.
• Components would pass the global state as properties to children
who often did nothing but just pass that state on again to their own
children
• It felt messy. It was!
Enter Flux
• Flux is Facebook’s answer to storing global state in React apps
• Flux data is kept in “stores”, which announce changes to React
components, which can update stores with “actions”
• A dispatcher ensures that updates that depend on other updates
don’t get deadlocked
What did we lose?
Here’s how the application has gotten worse:
• Application is considerably more complicated than when it was just
Rails and jQuery (this would have happened with any of the other JS
frameworks)
• More places things can break in our code
• React is much harder to test well than Rails with Capybara
• Some co-mingling of business logic and our presentation layer
What did we accomplish?
Here’s how the application has improved:
• Application is much faster from the user’s perspective
• Feels more “native”, with no reloads and quick changes after clicks
• App is useful when the network isn't available
• Ruby is responsible for our server-side work, and JavaScript owns
the browser and presentation layer
Further reading
• React.js: https://facebook.github.io/react/
• Flux: https://facebook.github.io/flux/
• Marty.js (a great Flux implementation): http://martyjs.org/
• React-rails gem
(mount React components in views, compile JSX in asset pipeline):
https://github.com/reactjs/react-rails
Questions?
I’m happy to answer questions about:
• React and Flux
• Ruby or Rails
• Cortex
• Working as a developer
• The awful state of the Boston Red Sox’ starting rotation
Thanks!
Feel free to get in touch!
twitter: @geoffharcourt
github: geoffharcourt
email: [email protected]