High Performance web apps in Om, React and ClojureScript

10,370 views
9,798 views

Published on

Talk given at YOW! LambdaJam, Brisbane 2014

Published in: Technology

High Performance web apps in Om, React and ClojureScript

  1. 1. High Performance Web UI's with Om and React LambdaJam - Brisbane, 2014 Leonardo Borges @leonardo_borges www.leonardoborges.com www.thoughtworks.com
  2. 2. About ‣ ThoughtWorker ‣ Functional Programming & Clojure advocate ‣ Founder of the Sydney Clojure User Group ‣ Currently writing “Clojure Reactive Programming”
  3. 3. Functional Programming is on the rise
  4. 4. And that makes us happy
  5. 5. However if you do client-side web development, you’re out of luck
  6. 6. Because Javascript… [1,3,5].map(parseInt);! // [1, NaN, NaN]!
  7. 7. There are options
  8. 8. Today we’ll see one of them: Clojurescript
  9. 9. 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
  10. 10. 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
  11. 11. 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
  12. 12. 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
  13. 13. A simple React component var HelloMessage = React.createClass({! displayName: 'HelloMessage',! render: function() {! return React.DOM.div(null, "Hello ", this.props.name);! }! });! ! React.renderComponent(! HelloMessage( {name:"John"} ), mountNode);
  14. 14. No literals?
  15. 15. 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);
  16. 16. React components are functions from application state to a DOM tree
  17. 17. Now let’s take a leap and look at the same component, written in Om
  18. 18. 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"))})!
  19. 19. Om/React’s component lifecycle IWillMountIInitState IShouldUpdate IRender IRenderState
  20. 20. 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
  21. 21. IInitState & IRenderState ‣ Initialise component local state using IInitState ‣ Use IRenderState to work with it and render the component
  22. 22. 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"))})!
  23. 23. IRender ‣ Same as IRenderState… ‣ …except it doesn’t depend on the component local state to render
  24. 24. 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"))})!
  25. 25. A larger example
  26. 26. A larger example
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. 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
  31. 31. 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))))))))!
  32. 32. 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
  33. 33. What about “undo” and “reset”?
  34. 34. 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))))!
  35. 35. Implementing reset (defn reset-app-state []! (reset! app-state (first @app-history))! (reset! app-history [@app-state]))!
  36. 36. Om/React components are functions from state to DOM trees
  37. 37. With immutable data structures we can access every version of the application state
  38. 38. So we simply update the application state, causing the components to get re-rendered
  39. 39. A bit of live coding
  40. 40. 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
  41. 41. 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
  42. 42. 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
  43. 43. Thanks! Questions? Leonardo Borges @leonardo_borges www.leonardoborges.com www.thoughtworks.com

×