August 3, 2019
By: Kevin

Reagent文档: Batching and Timing

https://github.com/reagent-project/reagent/blob/master/doc/BatchingAndTiming.md

state变化 -> UI变化

react会尽可能批量处理render, 批量render是react高性能的重要原因. 多次对程序状态的修改, 可能会体现为一次页面的更新. 页面(Dom)重绘的时机是浏览器自己控制的, 在'适当时机'更新. 这种行为有诸多好处

  1. 程序状态,比如一个atom的值的修改可以发生的非常频繁,页面更新是跟不上的. Reagent对状态的修改不会直接触发更新.
  2. 如果多个对atom的更新同时发生,它们有可能只会引发一次页面更新.
  3. 状态的修改和页面的重绘是异步的,修改的代码可以在event handler, 也可以在core.asyn的某个block, 非常灵活.
  4. 重绘机制可以和Reagent的requestAnimationFrame一起使用,动画更加顺滑.

关于异步重绘和以前同步绘制的比较在David Nolen(cljs的主要维护者)的一篇blog里有充分的比较.

react

React的情况 backbone

BackBone的情况

接下来是一个例子, 滑动rgb的滑杆, 下面会生成一定数量(数量可控)的颜色div. 我的电脑上2017 macbook: 2.3 GHz Intel Core i5上.

  • 在有1000个div的时候, 变一下颜色需要40ms左右,
  • 改变数量的时候只需要4ms!!!
(require '[reagent.core :as r])
(defn timing-wrapper [f]
  (let [start-time (r/atom nil)
        render-time (r/atom nil)
        now #(.now js/Date)
        start #(reset! start-time (now))
        stop #(reset! render-time (- (now) @start-time))
        timed-f (with-meta f
                  {:component-will-mount start
                   :component-will-update start
                   :component-did-mount stop
                   :component-did-update stop})]
    (fn []
      [:div
       [:p [:em "render time: " @render-time "ms"]]
       [timed-f]])))

(def base-color (r/atom {:red 130 :green 160 :blue 120}))
(def ncolors (r/atom 20))
(def random-colors (r/atom nil))

(defn to-rgb [{:keys [red green blue]}]
  (let [hex #(str (if (< % 16) "0")
                  (-> % js/Math.round (.toString 16)))]
    (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)}))

(defn reset-random-colors [color]
  (reset! random-colors
          (repeatedly #(-> color tweak-color to-rgb))))

(defn color-choose [color-part]
  [:div.color-slider
   (name color-part) " " (color-part @base-color)
   [:input {:type "range" :min 0 :max 255
            :value (color-part @base-color)
            :on-change (fn [e]
                         (swap! base-color assoc
                                color-part (-> e .-target .-value int))
                         (reset-random-colors @base-color))}]])

(defn ncolors-choose []
  [:div.color-slider
   "number of color divs " @ncolors
   [:input {:type "range" :min 0 :max 2000
            :value @ncolors
            :on-change #(reset! ncolors (-> % .-target .-value int))}]])

(defn color-plate [color]
  [:div
   {:style {:background-color color}}
   color])

(defn palette []
  (let [color @base-color
        n @ncolors]
    [:div
     [:p "base color: "]
     [color-plate (to-rgb color)]
     [:div.color-samples
      [:p n " random matching colors:"]
      (map-indexed (fn [k v]
                     ^{:key k} [color-plate v])
                   (take n @random-colors))]]))

(defn color-demo []
  (reset-random-colors @base-color)
  (fn []
    [:div
     [:h2 "Matching colors"]
     [color-choose :red]
     [color-choose :green]
     [color-choose :blue]
     [ncolors-choose]
     [timing-wrapper palette]]))

Tags: Reagent clojurescript