Om -- ClojureScript interface to Facebook's React
ClojureScript is a new compiler for Clojure that targets JavaScript. It is designed to emit JavaScript code which is compatible with the advanced compilation mode of the Google Closure optimizing compiler.
To get started, I highly recommend taking a guided tour of the ClojureScript language.
For experimenting with ClojureScript, try ClojureScript Web REPL.
More
- For a quick overview of the ClojureScript language (compared to JavaScript language constructs) see: HIMERA - Translations from JavaScript.
- A terse ClojureScript cheat sheet is available here.
- Since ClojureScript essentially is Clojure, you may also look into the Clojure cheat sheet and Clojure docs.
You may also want to check out this series of ClojureScript tutorials and ClojureScript One.
ClojureScript projects mostly use Leiningen for automation.
React is a JavaScript library for building user interfaces. It's declarative, efficient, and extremely flexible. What's more, it works with the libraries and frameworks that you already know.
React's own summary
- Just the UI: Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.
- Virtual DOM: React uses a virtual DOM diff implementation for ultra-high performance. It can also render on the server using Node.js — no heavy browser DOM required.
- Data flow: React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding.
- Components
- React's basic building blocks are called components
- each component knows how to
render
itself, i.e. markup/templates live within the component source (as opposed to most AngularJS views) - components can be nested of course
- Props
- React components can pass properties, short props, to child components
- props can be used to adapt rendering, fill out templates:
<p>{this.props.message}</p>
- props are immutable, passed from the parent and "owned" by the parent
- State
- for interactivity, components have a mutable, private state
- mutable via
this.setState()
- components re-render themselves when their state is updated
- more on how state works, what components should have state?, what should/shouldn't go in state?
- Events and Refs
- component event handlers can be attached to elements within the JSX template code of a component:
<form onSubmit={this.handleSubmit}>…</form>
- components can refer to child elements via
this.refs
, names are assigned through theref
attribute:<input type="text" ref="message">
- component event handlers can be attached to elements within the JSX template code of a component:
- JSX
- transforms from an XML-like syntax into native JavaScript
var link = <a href="http://facebook.github.io/react/">Hello React!</a>;
- becomes
var link = React.DOM.a({href: 'http://facebook.github.io/react/'}, 'Hello React!');
- Reconciliation
- "React's key design decision is to make the API seem like it re-renders the whole app on every update"
- …however, it actually performs a virtual DOM diff, inserting, deleting and updating nodes as needed
- this process is called reconciliation
<html>
<head>…</head>
<body>
<div id="my-view"></div> | rendering target
<script type="text/jsx">
/**
* @jsx React.DOM | JSX pragma
*/
var DemoButton = React.createClass({ + create a React component
getInitialState: function() { | component spec
return {wasClicked: false}; |
}, |
getDefaultProps: function() {} |
mixins: […] |
statics: { |
customMethod: function() {} |
}
componentWillMount: function() {}, | component lifecycle methods
componentDidMount: … |
componentWillReceiveProps: … |
shouldComponentUpdate: … |
componentWillUpdate: … |
componentDidUpdate: … |
componentWillUnmount: … |
handleClick: function() { | event handler
var name = this.refs.name.getDomNode.value(); |
alert('Hello ' + name + '!'); |
this.setState({wasClicked: true}); |
}, |
render: function() { |
return ( | JSX transforms this into plain JavaScript
<input type="text" ref="name"> |
<button onClick={this.handleClick}> |
{this.props.label.toUpperCase()} |
</button> |
{this.state.wasClicked} |
); |
} |
}); |
React.renderComponent( + render an instance of the component
DemoButton({label: 'Say hello!'}), |
document.getElementById('my-view') |
); |
</script>
</body>
</html>
-- see tutorial, component specifications and lifecycle.
- Coming from AngularJS? Consider reading this and this brief comparison
- See the React tutorial
- React Motivation
- Thinking in React
- See a list of React Tools on GitHub
- npm package: react-tools (command-line tools)
- chrome extension: React Developer Tools
- tm-language syntax highlighting and more tooling: React Tooling Integration
A ClojureScript interface to Facebook's React
TL;DR
- immutability of Clojure(Script) data-structures enables efficient DOM diffing (see also React docs)
- React builds virtual DOM lazily for diffing, only looking at subtrees which changed
shouldComponentUpdate
only needs to check reference equality- as opposed to JavaScript, where objects and arrays are mutable
- no
setState
or other subtree modifications, but efficient batched updates - we always re-render from the root
- bonus: all of important app state lies in a single piece of data (atom)
- which means we can serialize app state
- get snapshotting and undo "for free"
Excerpt from the original post
Modifying and querying the DOM is a huge performance bottleneck, and React embraces an approach that eschews this without sacrificing expressivity. It presents a well-designed object-oriented interface, but everything underneath the hood has been crafted with the eye of a pragmatic functional programmer. It works by generating a virtual version of the DOM and, as your application state evolves, it diffs changes between the virtual DOM trees over time. It uses these diffs to make the minimal set of changes required on the real DOM so you don't have to.
When React does a diff on the virtual DOM specified by your components, there is a very critical function - shouldComponentUpdate. If this returns false, React will never compute the children of the component. That is, React builds the virtual DOM tree lazily for diffing based on what path in the tree actually changed.
As it turns out, the default shouldComponentUpdate implementation is extremely conservative, because JavaScript devs tend to mutate objects and arrays! So in order to determine if some properties of a component have changed, they have to manually walk JavaScript objects and arrays to figure this out.
Instead of using JavaScript objects, Om uses ClojureScript data structures which we know will not be changed. Because of this, we can provide a component that implements shouldComponentUpdate by doing the fastest check possible - a reference equality check. This means we can always determine if the paths changed, starting from the root, in logarithmic time.
Thus we don't need React operations like setState, which exists to support both efficient subtree updating as well as good object-oriented style. Subtree updating for Om starting from root is always lightning fast because we're just doing reference equality checks all the way down.
Also, because we always re-render from the root, batched updates are trivial to implement. We don't bother with the batched update support in React, as it's designed to handle cases we don't care about, so we can just use our own 6-line rocket fuel enhancement.
Finally, because we always have the entire state of the UI in a single piece of data, we can trivially serialize all of the important app state - we don't need to bother with serialization protocols, or making sure that everyone implements them correctly. Om UI states are always serializable, always snapshottable.
This also means that Om UIs get undo for free. You can simply snapshot any state in memory and reinstate it whenever you like. It's memory-efficient, as ClojureScript data structures work by sharing structure.
-- David Nolen, The Future of JavaScript MVC Frameworks, 17 December 2013
Adapted from Om Basic Tutorial
index.html
<html>
<head>…</head>
<body>
<div id="app"></div>
<script src="//fb.me/react-0.10.0.js"></script>
<script src="demo.js"></script>
</body>
</html>
demo.cljs (compiles to demo.js)
(ns demo.core
(:require [om.core :as om :include-macros true] ; require om.core and alias as om
[om.dom :as dom :include-macros true])) ; require om.dom and alias as dom
;; Initialize app state
(def app-state
(atom
{:contacts
[{:first "Ben" :last "Bitdiddle" :email "[email protected]"}
{:first "Alyssa" :middle-initial "P" :last "Hacker" :email "[email protected]"}
{:first "Eva" :middle "Lu" :last "Ator" :email "[email protected]"}
{:first "Louis" :last "Reasoner" :email "[email protected]"}
{:first "Cy" :middle-initial "D" :last "Effect" :email "[email protected]"}
{:first "Lem" :middle-initial "E" :last "Tweakit" :email "[email protected]"}]}))
;; Helpers
(defn middle-name [{:keys [middle middle-initial]}]
(cond
middle (str " " middle)
middle-initial (str " " middle-initial ".")))
(defn display-name [{:keys [first last] :as contact}]
(str last ", " first (middle-name contact)))
;; Components
(defn contact-view [contact owner] ; sub-component
(reify
om/IRender
(render [this]
(dom/li nil (display-name contact)))))
(defn contacts-view [app owner] ; root component
(reify
om/IRender
(render [this]
(dom/div nil
(dom/h2 nil "Contact list")
(apply dom/ul nil
(om/build-all contact-view (:contacts app)))))))
;; Start
(om/root ; establish render loop
contacts-view ; - top-level rendering function
app-state ; - watched app/root component state
{:target (. js/document (getElementById "app"))}) ; - specify target element and other options
Explanation
om.core
- IRender, IRenderState, IWillMount, IDidMount… protocols
root
,build
,build-all
…
om.core/root
- establishes a Om rendering loop on a specific element in the DOM
- Om tracks global application state changes
- when the state changes schedules re-rendering via
requestAnimationFrame
, see om/core.cljs- fallback to
setTimeout
for browsers which do not supportrequestAnimationFrame
- fallback to
- ⇒ batch updates
om.dom
- provides direct access to
React.DOM
, e.g.om.dom/div
becomesReact.DOM.div
- provides direct access to
- Read about the Motivation for Om
- For an intro to Om, consider going through basic tutorial and intermediate tutorial.
- See the Om documentation, for Om namespaces, protocols and related functions.
- See Clojure Atoms documentation (used for app states)
- Time-travel with Om
- React browser support
- Source maps (ClojureScript)
- supported, see ClojureScript GitHub Wiki
- Tool support
- Interoperability (JS)
- see Himera Synonym
- you can create JS objects from ClojureScript,
- …access JS object properties,
- …call JS object methods,
- …call ClojureScript from JS
- Integration with Rails
- Clementine (ClojureScript on Rails Asset Pipeline)
- requires spinning up JVM whenever code ClojureScript changes with CRuby
- ⇒ JRuby
- AngularJS + ClojureScript could be a problem
- Server vs. client language disparity
- Server-side rendering
- V8 to eval ClojureScript compiled to JS ⇒ precompilation
- JRuby?
- Testing
- React Test Utilities
- simulate events
- mock components
- assertions
- Testing with ClojureScript code using Jasmine
- clojurescript.test
- …
- React Test Utilities
For some insights on server-side rendering of pages using React, reading the following article may be a good start: http://augustl.com/blog/2014/jdk8_react_rendering_on_server/