2014-01-30 21:29:42 +00:00
|
|
|
|
(ns reagentdemo.news.async
|
|
|
|
|
(:require [reagent.core :as reagent :refer [atom]]
|
2014-11-29 17:30:24 +00:00
|
|
|
|
[reagent.interop :refer-macros [.' .!]]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[reagent.debug :refer-macros [dbg println]]
|
2014-12-07 18:26:20 +00:00
|
|
|
|
[reagentdemo.syntax :as s :include-macros true]
|
2014-11-29 18:51:45 +00:00
|
|
|
|
[sitetools :as tools :refer [link]]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[reagentdemo.common :as common :refer [demo-component]]))
|
|
|
|
|
|
2014-11-29 17:30:24 +00:00
|
|
|
|
(def url "news/reagent-is-async.html")
|
2014-11-29 18:51:45 +00:00
|
|
|
|
(def title "Faster by waiting")
|
2014-11-29 17:30:24 +00:00
|
|
|
|
|
2014-12-07 18:26:20 +00:00
|
|
|
|
(def ns-src (s/syntaxed "(ns example
|
|
|
|
|
(:require [reagent.core :as reagent :refer [atom]]))"))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
2014-02-11 12:58:50 +00:00
|
|
|
|
(defn timing-wrapper [f]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
(let [start-time (atom nil)
|
|
|
|
|
render-time (atom nil)
|
|
|
|
|
now #(.now js/Date)
|
2014-02-03 07:31:35 +00:00
|
|
|
|
start #(reset! start-time (now))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
stop #(reset! render-time (- (now) @start-time))
|
|
|
|
|
timed-f (with-meta f
|
2014-02-03 07:31:35 +00:00
|
|
|
|
{:component-will-mount start
|
2014-01-30 21:29:42 +00:00
|
|
|
|
:component-will-update start
|
|
|
|
|
:component-did-mount stop
|
|
|
|
|
:component-did-update stop})]
|
2014-02-11 12:58:50 +00:00
|
|
|
|
(fn []
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[:div
|
|
|
|
|
[:p [:em "render time: " @render-time "ms"]]
|
2014-02-11 12:58:50 +00:00
|
|
|
|
[timed-f]])))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
|
|
|
|
(def base-color (atom {:red 130 :green 160 :blue 120}))
|
|
|
|
|
(def ncolors (atom 20))
|
|
|
|
|
(def random-colors (atom nil))
|
|
|
|
|
|
|
|
|
|
(defn to-rgb [{:keys [red green blue]}]
|
2014-02-03 07:31:35 +00:00
|
|
|
|
(let [hex #(str (if (< % 16) "0")
|
|
|
|
|
(-> % js/Math.round (.toString 16)))]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
(str "#" (hex red) (hex green) (hex blue))))
|
|
|
|
|
|
|
|
|
|
(defn tweak-color [{:keys [red green blue]}]
|
|
|
|
|
(let [rnd #(-> (js/Math.random) (* 256))
|
|
|
|
|
tweak #(-> % (+ (rnd)) (/ 2) js/Math.floor)]
|
|
|
|
|
{:red (tweak red) :green (tweak green) :blue (tweak blue)}))
|
|
|
|
|
|
2014-02-10 22:08:20 +00:00
|
|
|
|
(defn reset-random-colors [color]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
(reset! random-colors
|
2014-02-10 22:08:20 +00:00
|
|
|
|
(repeatedly #(-> color tweak-color to-rgb))))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
2014-02-11 12:58:50 +00:00
|
|
|
|
(defn color-choose [color-part]
|
2014-02-03 07:31:35 +00:00
|
|
|
|
[:div.color-slider
|
|
|
|
|
(name color-part) " " (color-part @base-color)
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[:input {:type "range" :min 0 :max 255
|
|
|
|
|
:value (color-part @base-color)
|
2014-02-03 07:31:35 +00:00
|
|
|
|
:on-change (fn [e]
|
|
|
|
|
(swap! base-color assoc
|
|
|
|
|
color-part (-> e .-target .-value int))
|
2014-02-10 22:08:20 +00:00
|
|
|
|
(reset-random-colors @base-color))}]])
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
|
|
|
|
(defn ncolors-choose []
|
2014-02-03 07:31:35 +00:00
|
|
|
|
[:div.color-slider
|
|
|
|
|
"number of color divs " @ncolors
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[:input {:type "range" :min 0 :max 500
|
|
|
|
|
:value @ncolors
|
|
|
|
|
:on-change #(reset! ncolors (-> % .-target .-value))}]])
|
|
|
|
|
|
2014-02-11 19:56:48 +00:00
|
|
|
|
(defn color-plate [color]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[:div.color-plate
|
|
|
|
|
{:style {:background-color color}}])
|
|
|
|
|
|
|
|
|
|
(defn palette []
|
|
|
|
|
(let [color @base-color
|
|
|
|
|
n @ncolors]
|
|
|
|
|
[:div
|
2014-12-07 20:07:32 +00:00
|
|
|
|
[:p "base color: "]
|
|
|
|
|
[color-plate (to-rgb color)]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[:div.color-samples
|
|
|
|
|
[:p n " random matching colors:"]
|
|
|
|
|
(map-indexed (fn [k v]
|
2014-02-12 06:16:55 +00:00
|
|
|
|
^{:key k} [color-plate v])
|
2014-01-30 21:29:42 +00:00
|
|
|
|
(take n @random-colors))]]))
|
|
|
|
|
|
|
|
|
|
(defn color-demo []
|
2014-02-10 22:08:20 +00:00
|
|
|
|
(reset-random-colors @base-color)
|
2014-01-30 21:29:42 +00:00
|
|
|
|
(fn []
|
|
|
|
|
[:div
|
|
|
|
|
[:h2 "Matching colors"]
|
2014-02-11 12:58:50 +00:00
|
|
|
|
[color-choose :red]
|
|
|
|
|
[color-choose :green]
|
|
|
|
|
[color-choose :blue]
|
2014-01-30 21:29:42 +00:00
|
|
|
|
[ncolors-choose]
|
2014-02-11 12:58:50 +00:00
|
|
|
|
[timing-wrapper palette]]))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
2014-02-03 09:20:05 +00:00
|
|
|
|
(defn main [{:keys [summary]}]
|
2014-02-03 07:31:35 +00:00
|
|
|
|
(let [om-article {:href "http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/"}]
|
2014-02-03 09:20:05 +00:00
|
|
|
|
[:div.reagent-demo
|
2014-11-29 18:51:45 +00:00
|
|
|
|
[:h1 [link {:href url} title]]
|
2014-02-03 09:20:05 +00:00
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Reagent gets async rendering"]
|
|
|
|
|
|
|
|
|
|
[:p "Reagent already separates state from components. Now they
|
|
|
|
|
are also separated in time."]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-02-03 09:20:05 +00:00
|
|
|
|
[:p "From version 0.3.0, changes in application state (as
|
|
|
|
|
represented by Reagent’s " [:code "atom"] "s) are no longer
|
|
|
|
|
rendered immediately to the DOM. Instead, Reagent waits until
|
|
|
|
|
the browser is ready to repaint the window, and then all the
|
|
|
|
|
changes are rendered in one single go."]
|
|
|
|
|
|
|
|
|
|
(if summary
|
2014-11-29 17:30:24 +00:00
|
|
|
|
[link {:href url
|
2014-02-03 09:20:05 +00:00
|
|
|
|
:class 'news-read-more} "Read more"]
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
|
|
|
|
|
[:p "This is good for all sorts of reasons:"]
|
|
|
|
|
[:ul
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-02-03 09:20:05 +00:00
|
|
|
|
[:li "Reagent doesn't have to spend time doing renderings
|
|
|
|
|
that no one would ever see (because changes to application
|
|
|
|
|
state happened faster than the browser could repaint)."]
|
|
|
|
|
|
|
|
|
|
[:li "If two or more atoms are changed simultaneously, this
|
|
|
|
|
now leads to only one re-rendering, and not two."]
|
|
|
|
|
|
|
|
|
|
[:li "The new code does proper batching of renderings even
|
|
|
|
|
when changes to atoms are done outside of event
|
|
|
|
|
handlers (which is great for e.g core.async users)."]
|
|
|
|
|
|
|
|
|
|
[:li "Repaints can be synced by the browser with for example
|
|
|
|
|
CSS transitions, since Reagent uses requestAnimationFrame
|
|
|
|
|
to do the batching. That makes for example animations
|
|
|
|
|
smoother."]]
|
|
|
|
|
|
|
|
|
|
[:p "In short, Reagent renders less often, but at the right
|
|
|
|
|
times. For a much better description of why async rendering
|
|
|
|
|
is good, see David Nolen’s " [:a om-article "excellent
|
|
|
|
|
explanation here."]]
|
|
|
|
|
|
|
|
|
|
[:h2 "The bad news"]
|
|
|
|
|
|
|
|
|
|
[:p "Lunches in general tend to be non-free, and this is no
|
|
|
|
|
exception… The downside to async rendering is that you can no
|
|
|
|
|
longer depend on changes to atoms being immediately available
|
|
|
|
|
in the DOM. (Actually, you couldn’t before either, since
|
|
|
|
|
React.js itself does batching inside event handlers.)"]
|
|
|
|
|
|
|
|
|
|
[:p "This may make testing a bit more verbose: you now have
|
|
|
|
|
to call " [:code "reagent.core/flush"] " to force Reagent to
|
|
|
|
|
synchronize state with the DOM."]
|
|
|
|
|
|
|
|
|
|
[:h2 "An example"]
|
|
|
|
|
|
|
|
|
|
[:p "Here is an example to (hopefully) demonstrate the
|
|
|
|
|
virtues of async rendering. It consists of a simple color
|
|
|
|
|
chooser (three sliders to set the red, green and blue
|
|
|
|
|
components of a base color), and shows the base color + a
|
|
|
|
|
bunch of divs in random matching colors. As soon as the base
|
|
|
|
|
color is changed, a new set of random colors is shown."]
|
|
|
|
|
|
|
|
|
|
[:p "If you change one of the base color components, the base
|
|
|
|
|
color should change immediately, and smoothly (on my Macbook
|
|
|
|
|
Air, rendering takes around 2ms, with 20 colored divs
|
|
|
|
|
showing)."]
|
|
|
|
|
|
|
|
|
|
[:p "But perhaps more interesting is to see what happens when
|
|
|
|
|
the updates can’t be made smoothly (because the browser
|
|
|
|
|
simply cannot re-render the colored divs quickly enough). On
|
|
|
|
|
my machine, this starts to happen if I change the number of
|
|
|
|
|
divs shown to above 150 or so."]
|
|
|
|
|
|
|
|
|
|
[:p "As you increase the number of divs, you’ll notice that
|
|
|
|
|
the base color no longer changes quite so smoothly when you
|
|
|
|
|
move the color sliders."]
|
|
|
|
|
|
|
|
|
|
[:p "But the crucial point is that the sliders "
|
|
|
|
|
[:strong "still work"] ". Without async rendering, you could
|
|
|
|
|
quickly get into a situation where the browser hangs for a
|
|
|
|
|
while, doing updates corresponding to an old state. "]
|
|
|
|
|
|
|
|
|
|
[:p "With async rendering, the only thing that happens is
|
|
|
|
|
that the frame rate goes down."]
|
|
|
|
|
|
|
|
|
|
[:p "Btw, I find it quite impressive that React manages to
|
|
|
|
|
change 500 divs (12 full screens worth) in slightly more than
|
|
|
|
|
40ms. And even better: when I change the number of divs
|
|
|
|
|
shown, it only takes around 6ms to re-render the color
|
|
|
|
|
palette (because the individual divs don’t have to be
|
|
|
|
|
re-rendered, divs are just added or removed from the DOM as
|
|
|
|
|
needed)."]
|
|
|
|
|
|
|
|
|
|
[demo-component
|
|
|
|
|
{:comp color-demo
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src [:pre
|
|
|
|
|
ns-src
|
|
|
|
|
(s/src-of [:timing-wrapper :base-color :ncolors
|
|
|
|
|
:random-colors :to-rgb :tweak-color
|
|
|
|
|
:reset-random-colors :color-choose
|
2014-12-12 08:42:24 +00:00
|
|
|
|
:ncolors-choose :color-plate
|
|
|
|
|
:palette :color-demo])]}]])]]))
|
2014-01-30 21:29:42 +00:00
|
|
|
|
|
2014-11-29 18:51:45 +00:00
|
|
|
|
(tools/register-page url (fn [] [main])
|
|
|
|
|
(str "Reagent: " title))
|