The Case for React.js and ClojureScript Murilo Pereira @mpereira May 2, 2014
Aug 28, 2014
The Case for
React.js andClojureScript
Murilo Pereira
@mpereira
May 2, 2014
Problem
Building UIs isdifficult.
Modern Web UIsvisual representations of data changing over timerespond to asynchronous user eventstransition both the underlying data and itself to new states
"Data changing over time is theroot of all evil."
Incidental ComplexityJavaScript isn't DOM is stateful
reactive
What if the JavaScript DOMAPI was reactive?
function tweetScore(tweet) { return(tweet.favorites_count + tweet.retweets_count);}
function compareTweetsByScore(a, b) { return(tweetScore(b) - tweetScore(a));}
function renderApplication(tweets) { return(document.dom.ul( { class: 'tweets' }, tweets .sort(compareTweetsByScore) .slice(0, 5) .map(function(tweet) { return(document.dom.li({ class: 'tweet' }, tweet.text); }) ));}
var tweets = fetchTweets({ username: 'mpereira' }, { limit: 20 });document.dom.render(renderApplication(tweets), document.body);
Possible solution: Data binding
Data BindingObservablesComputed properties
Backbone, Ember, Meteor, et al.
Contemporary data binding isnot simple.
SimpleNot to be mistaken for "easy"
Easiness = Familiarity (subjective)
Simplicity = "Does/is one thing" (objective)
Data BindingApplication logic entangled with observablesForces us to compose our programs with frameworkconstructs instead of language constructs (functions and datastructures)
"How often are you fighting theframework?"
Dirty-checking (Angular)Also susceptible to the problems of data binding.
https://docs.angularjs.org/guide/concepts
Complex
Even with shortcomings it'sstill possible to build complex,
modern UIs usingcontemporary MVC
frameworks.
"We can create precisely thesame programs we're creating
right now with drasticallysimpler tools."
Rich Hickey
A different solution to the sameproblem.
React.js
React.jsLibrary for creating UIsRenders the DOM and responds to user eventsCan be thought of as the V in MVC
Remember our utopic examplea few slides back?
function tweetScore(tweet) { return(tweet.favorites_count + tweet.retweets_count);}
function compareTweetsByScore(a, b) { return(tweetScore(b) - tweetScore(a));}
function renderApplication(tweets) { return(document.dom.ul( { class: 'tweets' }, tweets .sort(compareTweetsByScore) .slice(0, 5) .map(function(tweet) { return(document.dom.li({ class: 'tweet' }, tweet.text); }) ));}
var tweets = fetchTweets({ username: 'mpereira' }, { limit: 20 });document.dom.render(renderApplication(tweets), document.body);
It's actually valid React.js code
function tweetScore(tweet) { return(tweet.favorites_count + tweet.retweets_count);}
function compareTweetsByScore(a, b) { return(tweetScore(b) - tweetScore(a));}
function renderApplication(tweets) { return(React.DOM.ul( { className: 'tweets' }, tweets .sort(compareTweetsByScore) .slice(0, 5) .map(function(tweet) { return(React.DOM.li({ className: 'tweet' }, tweet.text); }) ));}
var tweets = fetchTweets({ username: 'mpereira' }, { limit: 20 });React.renderComponent(renderApplication(tweets), document.body);
React gives us a minimallyleaky abstraction for a reactiveJavaScript/DOM environment.
Data
Component
DOM
Model
View
DOM
M3
V2
DOM
M2 M4 M5M1
V1 V3
Data
C3
DOM
C4C2 C5C1
Components
ComponentsIdempotent functions that describe your
UI at any point in time.
component(data) = VDOM
component(data_1) = VDOM_1
*user input changes data from data_1 to data_2*
component(data_2) = VDOM_2diffVDOMs(VDOM_1, VDOM_2) = diffDOMOperations(diff) = operations
applyDOMOperations(operations, document.body)
Best part? You don't even haveto worry about this. Just build
components.
Every place data is displayed isguaranteed to be up-to-date.
No need for KVO or markingHTML templates withframework directives.
Frees the programmer fromdoing manual, explicit DOM
operations.
But what about theperformance?
Isn't diffing VDOMs slow?
Why have VDOMs if thebrowser already has a DOM?
The DOM is slow.
The DOM is slowQuerying may require tree traversalMutations trigger viewport reflows, relayouts, repaints (CSSengine, etc.)Can also invalidate caches, requiring the entire DOM to bereconstructed on the viewport
Having in-memoryrepresentations of the DOM
allows React to have extremelyfast UIs.
Virtual DOM (VDOM)Allows for components to have a declarative APIPerforms the minimum amount of actual DOM operationsthrough computing diffsHandles DOM operations for you
"Performance isn't the [main]goal of the VDOM, but if you'reworried with performance,
most workloads are as fast orfaster [than the MVC
alternatives] out of the box."Pete Hunt
Components
Components allow you toexpress your program in thelanguage of your problem
domain.
Rather than on the language ofa particular framework.
Bill O'Reilly
Tide comes in, tide goes out.You can't explain that!
Top Tweets
Just had #breakfast.
@troll yes, definitely plan onthat.
pic.twitter.com/asd23 #selfie#instagram
Good# morning.
Bill O'Reilly
Top Tweets
Tide comes in, tide goes out.You can't explain that! #tides
Just had #breakfast.
@troll yes, definitely plan onthat.
pic.twitter.com/asd23 #selfie#instagram
Good# morning.
Header
Profile
Tweets
Top Tweets
var Profile = React.createClass({ render: function() { return(React.DOM.div(null, [ React.DOM.img(null, this.props.user.image), React.DOM.p(null, this.props.user.name) ]); }});
var Tweets = React.createClass({ render: function() { return(React.DOM.ul(null, this.props.tweets.map(function(tweet) { return(React.DOM.li(null, tweet.text)); }); }});
var TopTweets = React.createClass({ render: function() { return(React.DOM.div(null, [ React.DOM.h1(null, 'Top Tweets'), Profile({ user: this.props.user }), Tweets({ tweets: this.props.user.tweets }) ]); }});
React.renderComponent(TopTweets({ user: user }), document.body);
var Profile = React.createClass({ render: function() { return( <div> <img href={this.props.user.image} /> <p>{this.props.user.name}</p> </div> ); }});
var Tweets = React.createClass({ render: function() { return( <ul> {this.props.tweets.map(function(tweet) { return(<li>tweet.text</li>); })}; </ul> ); }});
var TopTweets = React.createClass({ render: function() { return( <div> <h1>Top Tweets</h1> <Profile user={this.props.user} /> <Tweets tweets={this.props.user.tweets} /> </div> ); }
TopTweets(data) = Header() + Profile(data_1) + Tweets(data_2)
Components are reusable andcomposable declarative
representations of your UI.
CollateralBenefits
Collateral BenefitsRender the application on the server (node.js, Java 8'sNashhorn, etc.)UI testability for freeNo templates!
React.js takeawaysDeclarative, fast UIsExpress your programs in the language of your problemdomain
ClojureScript
ProblemJavaScript sucks
We need JavaScript
JavaScript sucksNo integersNo module systemVerbose syntaxConfusing, inconsistent equality operatorsConfusing, inconsistent automatic operator conversionsLack of block scopeNon-uniform iteratorsGlobal variables by defaultNaNthisNo macros (yet...)etc.
We Need JavaScriptRuntime is everywhereRuntime is improving (Web Sockets, geo-location, FS API, RAF,push notifications, WebRTC, WebGL, SVG, Canvas, WebWorkers, IndexedDB, etc.)
Build better languages on topof JavaScript.
Compilers!Lots of them already existOnly a few bring significant improvements
Popular ones
CoffeeScript: mostly syntax sugarTypeScript: typed superset of JavaScript. Brings type checking,compile time errors
ClojureScript
ClojureScript is a Clojurecompiler that targets
JavaScript.
Why ClojureScript?Why Clojure?Why LISP?
Clojure is just a betterlanguage.
Number of daysspent designingthe language v1
JavaScript Clojure
ClojureScriptLISPSTMRuntime polymorphismThe REPLFunctional programmingImmutable data structuresUniform API over immutable data structuresMacrosLazy sequencesDestructuringState VS Identityetc.
core.async(go (try (let [tweets (<? (get-tweets-for "swannodette")) first-url (<? (expand-url (first (parse-urls tweets)))) response (<? (http-get first-url))] (. js/console (log "Most recent link text:" response))) (catch js/Error e (. js/console (error "Error with the twitterverse:" e)))))
Asynchronous Error Handling
The possibility of havingaccess to the power of Clojurein the browser is immensely
valuable.
ValueTo programmers, who will be able to build better programs, with
less bugs, faster.
To the people and companies who will benefit from thoseprograms.
The developer experience hasimproved significantly.
Developer ExperienceSource mapsEasily reproducible tutorialsBrowser-connected in-editor REPLsFast compilation cyclesCompatible libraries
Recommend you to check itout
(but you probably have, already)
Om
ClojureScript interface to React.
"Because of immutable dataOm can deliver even better
performance than React out ofthe box."
David Nolen
30-40X faster than JS MVCs nothing to do with ClojureScript. Ditch stateful objects, ditch events. Be declarative. Immutability. That's it.5:39 AM - 15 Dec 2013
David Nolen @swannodette
Follow
41 RETWEETS 39 FAVORITES
???
ClojureScriptCompiled, slower than hand-written JavaScriptUses immutable data structures, slower than JavaScript datastructures
And yet
Om's performance beats thatof most JavaScript MVC
frameworks
"Ideas have fundamentalperformance properties."
Pete Hunt
Immutable data structuresmake React's diffing algorithm
really smart.
Takeaways
"Ideas have fundamentalperformance properties."
"Don't trade simplicity forfamiliarity."
And .give it five minutes
Thanks! @mpereira
Learn React.jsWhy?
Be Predictable, Not CorrectReact: Rethinking Best PracticesWhy React?
How
Getting StartedIntroduction to React.jsTutorial
Learn ClojureScriptWhy?
ClojureScript RationaleThe Future of JavaScript MVC FrameworksClojure RationaleWhy Clojure?Beating the Averages
How
ClojureScript 101Translations from JavaScriptLight Table ClojureScript TutorialOm Tutorial