High Performance web apps in Om, React and ClojureScript

Post on 06-May-2015

6,575 views 2 download

description

Talk given at YOW! LambdaJam, Brisbane 2014

transcript

High Performance Web UI's with Om and React

LambdaJam - Brisbane, 2014

Leonardo Borges @leonardo_borges

www.leonardoborges.com www.thoughtworks.com

About‣ ThoughtWorker ‣ Functional Programming & Clojure advocate ‣ Founder of the Sydney Clojure User Group

‣ Currently writing “Clojure Reactive Programming”

Functional Programming is on the rise

And that makes us happy

However if you do client-side web development, you’re out of luck

Because Javascript…

[1,3,5].map(parseInt);!// [1, NaN, NaN]!

There are options

Today we’ll see one of them: Clojurescript

What we’ll see

‣ An Overview of React and how it enables fast rendering ‣ Meet Om, a ClojureScript interface to React ‣ Boosting React’s rendering performance with immutable data structures ‣ A simple demo featuring “data binding” and undo functionality

React

‣ Created by Facebook for building user interfaces ‣ The V in MVC ‣ Self-contained components ‣ Doesn’t make assumptions about your stack - can be used with anything

Self-contained components

‣ React combines display logic and DOM generation ‣ Components are themselves loosely coupled ‣ The whole app is re-rendered on every update ‣ Virtual DOM ‣ Efficient diff algorithm

Efficient diff algorithm

‣ Creates a virtual version of the DOM ‣ As the application state changes new DOM trees are generated ‣ React diffs the trees and computes the minimal set of changes ‣ Finally it applies the changes to the real DOM

A simple React componentvar HelloMessage = React.createClass({! displayName: 'HelloMessage',! render: function() {! return React.DOM.div(null, "Hello ", this.props.name);! }!});!!

React.renderComponent(! HelloMessage( {name:"John"} ), mountNode);

No literals?

A simple React component (using the JSX pre-processor)

var HelloMessage = React.createClass({! render: function() {! return <div>Hello {this.props.name}</div>;! }!});!!

React.renderComponent(! <HelloMessage name="John" />, mountNode);

React components are functions from application state to a DOM tree

Now let’s take a leap and look at the same component, written in Om

A simple Om component(def app-state (atom {:name "Leo"}))!!

(defn hello-message [app owner]! (reify om/IRender! (render [_]! (dom/div nil! (str "Hello " (:name app))))))!!

!

(om/root hello-message app-state! {:target (. js/document (getElementById "hello"))})!

Om/React’s component lifecycle

IWillMountIInitState IShouldUpdate

IRender

IRenderState

IShouldUpdate

‣ Called on app state changes but before rendering ‣ This is where React uses its fast diff algorithm ‣ Om components implement the fastest algorithm possible: a simple reference equality check ‣ Generally, you won’t have to implement this

IInitState & IRenderState

‣ Initialise component local state using IInitState ‣ Use IRenderState to work with it and render the component

IInitState & IRenderState(defn counter [app owner]! (reify! om/IInitState! (init-state [_]! {:clicks 0})! om/IRenderState! (render-state [_ state]! (dom/div nil! (str "Clicks " (:clicks state))! (dom/button #js {:onClick! #(om/set-state! owner :clicks (inc (:clicks state)))}! "Click me!")))))!!(om/root counter (atom {})! {:target (. js/document (getElementById "app"))})!

IRender

‣ Same as IRenderState… ‣ …except it doesn’t depend on the component local state to render

IRender(def app-state (atom {:name "Leo"}))!!

(defn hello-message [app owner]! (reify om/IRender! (render [_]! (dom/div nil! (str "Hello " (:name app))))))!!

!

(om/root hello-message app-state! {:target (. js/document (getElementById "hello"))})!

A larger example

A larger example

A reusable editable component(defn editable [text owner]! (reify! om/IInitState! (init-state [_]! {:editing false})! om/IRenderState! (render-state [_ {:keys [editing]}]! (dom/li nil! (dom/span #js {:style (display (not editing))} (om/value text))! (dom/input! #js {:style (display editing)! :value (om/value text)! :onChange #(handle-change % text owner)! :onKeyPress #(when (== (.-keyCode %) 13)! (commit-change text owner))! :onBlur (fn [e] (commit-change text owner))})! (dom/button! #js {:style (display (not editing))! :onClick #(om/set-state! owner :editing true)}! "Edit")))))!

From https://github.com/swannodette/om/wiki/Basic-Tutorial

A reusable editable component(defn editable [text owner]! (reify! om/IInitState! (init-state [_]! {:editing false})! om/IRenderState! (render-state [_ {:keys [editing]}]! (dom/li nil! (dom/span #js {:style (display (not editing))} (om/value text))! (dom/input! #js {:style (display editing)! :value (om/value text)! :onChange #(handle-change % text owner)! :onKeyPress #(when (== (.-keyCode %) 13)! (commit-change text owner))! :onBlur (fn [e] (commit-change text owner))})! (dom/button! #js {:style (display (not editing))! :onClick #(om/set-state! owner :editing true)}! "Edit")))))!

From https://github.com/swannodette/om/wiki/Basic-Tutorial

A reusable editable component(defn editable [text owner]! (reify! om/IInitState! (init-state [_]! {:editing false})! om/IRenderState! (render-state [_ {:keys [editing]}]! (dom/li nil! (dom/span #js {:style (display (not editing))} (om/value text))! (dom/input! #js {:style (display editing)! :value (om/value text)! :onChange #(handle-change % text owner)! :onKeyPress #(when (== (.-keyCode %) 13)! (commit-change text owner))! :onBlur (fn [e] (commit-change text owner))})! (dom/button! #js {:style (display (not editing))! :onClick #(om/set-state! owner :editing true)}! "Edit")))))!

From https://github.com/swannodette/om/wiki/Basic-Tutorial

The speakers view(defn speakers-view [app owner]! (reify! om/IRender! (render [_]! (dom/div nil! (dom/div #js {:id "speakers"! :style #js {:float "left"}}! (dom/h2 nil "Speakers")! (dom/button #js {:onClick undo} "Undo")! (dom/button #js {:onClick reset-app-state} "Reset")! (apply dom/ul nil! (om/build-all speaker-view (speakers app)! {:shared {:app-state app}})))! (om/build speaker-details-view app)))))

This is how you build components

The Sessions view

Same deal as before

(defn sessions-view [app owner]! (reify! om/IRender! (render [_]! (dom/div #js {:id "sessions"}! (dom/h2 nil "Sessions")! (apply dom/ul nil! (map #(om/build editable %) (vals (:sessions app))))))))!

Apps can have multiple roots

(om/root speakers-view app-state! {:target (. js/document (getElementById "speakers"))})!!

(om/root sessions-view app-state! {:target (. js/document (getElementById "sessions"))})!

You can have multiple “mini-apps” inside your main app

Makes it easy to try Om in a specific section/feature

What about “undo” and “reset”?

Implementing undo(def app-state (atom speaker-data))!(def app-history (atom [@app-state]))!!

(add-watch app-state :history! (fn [_ _ _ n]! (when-not (= (last @app-history) n)! (swap! app-history conj n))! (let [c (count @app-history)]! (prn c " Saved items in app history"))))!!

(defn undo []! (when (> (count @app-history) 1)! (swap! app-history pop)! (reset! app-state (last @app-history))))!

Implementing reset

(defn reset-app-state []! (reset! app-state (first @app-history))! (reset! app-history [@app-state]))!

Om/React components are functions from state to DOM trees

With immutable data structures we can access every version of the application state

So we simply update the application state, causing the components to get re-rendered

A bit of live coding

Summary

‣ With Om, you’re not using a crippled template language, you can leverage all of Clojurescript (including other DOM libraries) ‣ Rendering and display logic are inevitably coupled. Om/React acknowledges that a bundles them in components ‣ The whole app is re-rendered on every state change, making it easier to reason about ‣ This is efficient thanks to immutable data structures

Summary

‣ Clojurescript also provides a better development experience with a powerful browser REPL much like what you’d get with Clojure on the JVM ‣ Source maps are here today ‣ Bottom line is that Clojurescript is a serious contender

References

‣ Code: https://github.com/leonardoborges/lambdajam-2014-om-talk ‣ React documentation: http://facebook.github.io/react/ ‣ Om documentation: https://github.com/swannodette/om/wiki/Documentation#build ‣ Basic Tutorial: https://github.com/swannodette/om/wiki/Basic-Tutorial

Thanks! Questions?

Leonardo Borges @leonardo_borges

www.leonardoborges.com www.thoughtworks.com