From dfe0eb41d5792ae884ed80f5e6bfd5ac58b43dfd Mon Sep 17 00:00:00 2001 From: Dan Holmsand Date: Mon, 27 Jan 2014 16:17:37 +0100 Subject: [PATCH] Batch updates properly by rendering parents before children --- src/reagent/core.cljs | 1 + src/reagent/impl/component.cljs | 28 +++++++++++++++---------- src/reagent/impl/template.cljs | 23 ++++++++++++--------- test/testcloact.cljs | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/src/reagent/core.cljs b/src/reagent/core.cljs index 9306331..68d4c8b 100644 --- a/src/reagent/core.cljs +++ b/src/reagent/core.cljs @@ -44,6 +44,7 @@ looking like this: {:get-initial-state (fn [this]) :component-will-receive-props (fn [this new-props]) :should-component-update (fn [this old-props new-props old-children new-children]) +:component-did-mount (fn [this]) :component-did-update (fn [this old-props old-children]) :component-will-unmount (fn [this]) :render (fn [props children this])} diff --git a/src/reagent/impl/component.cljs b/src/reagent/impl/component.cljs index eaaf0d1..f134f08 100644 --- a/src/reagent/impl/component.cljs +++ b/src/reagent/impl/component.cljs @@ -2,7 +2,7 @@ (ns reagent.impl.component (:refer-clojure :exclude [flush]) (:require [reagent.impl.template :as tmpl - :refer [cljs-props cljs-children React]] + :refer [cljs-props cljs-children cljs-level React]] [reagent.impl.util :as util] [reagent.ratom :as ratom] [reagent.debug :refer-macros [dbg prn]])) @@ -51,17 +51,23 @@ (js/requestAnimationFrame f) (js/setTimeout f 16))) -(defn run-queue [v] - (doseq [C v] - (when-not (.-cljsIsDirty C) - (dbg C)) - (when (.-cljsIsDirty C) - (.forceUpdate C)))) +(defn compare-levels [c1 c2] + (- (-> c1 js-props (aget cljs-level)) + (-> c2 js-props (aget cljs-level)))) + +(defn run-queue [a] + ;; sort components by level, to make sure parents + ;; are rendered before children + (.sort a compare-levels) + (dotimes [i (alength a)] + (let [C (aget a i)] + (when (.-cljsIsDirty C) + (.forceUpdate C))))) (deftype RenderQueue [^:mutable queue ^:mutable scheduled?] Object (queue-render [this C] - (set! queue (conj queue C)) + (.push queue C) (.schedule this)) (schedule [this] (when-not scheduled? @@ -69,11 +75,11 @@ (next-tick #(.run-queue this)))) (run-queue [_] (let [q queue] - (set! queue (empty queue)) + (set! queue (array)) (set! scheduled? false) (run-queue q)))) -(def render-queue (RenderQueue. [] false)) +(def render-queue (RenderQueue. (array) false)) (defn flush [] (.run-queue render-queue)) @@ -90,7 +96,7 @@ ;; Call render function with props, children, component res (f props children C) conv (if (vector? res) - (tmpl/as-component res) + (tmpl/as-component res (aget p cljs-level)) (if (fn? res) (do-render C (set! (.-cljsRenderFn C) res)) res))] diff --git a/src/reagent/impl/template.cljs b/src/reagent/impl/template.cljs index 8a7eb35..a19ee2d 100644 --- a/src/reagent/impl/template.cljs +++ b/src/reagent/impl/template.cljs @@ -9,6 +9,7 @@ (def cljs-props "cljsProps") (def cljs-children "cljsChildren") +(def cljs-level "cljsLevel") (def isClient (not (nil? (try (.-document js/window) (catch js/Object e nil))))) @@ -64,10 +65,10 @@ (set-id-class objprops id-class)) objprops)))) -(defn map-into-array [f coll] +(defn map-into-array [f arg coll] (let [a (into-array coll)] (dotimes [i (alength a)] - (aset a i (f (aget a i)))) + (aset a i (f (aget a i) arg))) a)) (declare as-component) @@ -75,9 +76,10 @@ (defn wrapped-render [this comp id-class] (let [inprops (aget this "props") props (aget inprops cljs-props) + level (aget inprops cljs-level) hasprops (or (nil? props) (map? props)) jsargs (->> (aget inprops cljs-children) - (map-into-array as-component))] + (map-into-array as-component (inc level)))] (.unshift jsargs (convert-props props id-class)) (.apply comp nil jsargs))) @@ -139,7 +141,7 @@ (set! (.-cljsReactClass tag) (wrap-component tag nil nil)) (fn-to-class tag))))))) -(defn vec-to-comp [v] +(defn vec-to-comp [v level] (assert (pos? (count v))) (let [[tag props] v hasmap (map? props) @@ -147,14 +149,17 @@ c (as-class tag) jsprops (js-obj cljs-props (if hasmap props) cljs-children (if (> (count v) first-child) - (subvec v first-child)))] + (subvec v first-child)) + cljs-level level)] (when hasmap (let [key (:key props)] (when-not (nil? key) (aset jsprops "key" key)))) (c jsprops))) -(defn as-component [x] - (cond (vector? x) (vec-to-comp x) - (seq? x) (map-into-array as-component x) - true x)) +(defn as-component + ([x] (as-component x 0)) + ([x level] + (cond (vector? x) (vec-to-comp x level) + (seq? x) (map-into-array as-component level x) + true x))) diff --git a/test/testcloact.cljs b/test/testcloact.cljs index 2235f55..65d54f9 100644 --- a/test/testcloact.cljs +++ b/test/testcloact.cljs @@ -12,6 +12,8 @@ (def isClient (not (nil? (try (.-document js/window) (catch js/Object e nil))))) +(def rflush reagent/flush) + (defn add-test-div [name] (let [doc js/document body (.-body js/document) @@ -124,6 +126,40 @@ (is (= runs (running))) (is (= 2 @ran))))) +(deftest batched-update-test [] + (when isClient + (let [ran (atom 0) + v1 (atom 0) + v2 (atom 0) + c2 (fn [{val :val}] + (swap! ran inc) + (assert (= @v1 val)) + [:div @v2]) + c1 (fn [] + (swap! ran inc) + [:div @v1 + [c2 {:val @v1}]])] + (with-mounted-component [c1] + (fn [c div] + (rflush) + (is (= @ran 2)) + (swap! v2 inc) + (is (= @ran 2)) + (rflush) + (is (= @ran 3)) + (swap! v1 inc) + (rflush) + (is (= @ran 5)) + (swap! v2 inc) + (swap! v1 inc) + (rflush) + (is (= @ran 7)) + (swap! v1 inc) + (swap! v1 inc) + (swap! v2 inc) + (rflush) + (is (= @ran 9))))))) + (deftest init-state-test (when isClient (let [ran (atom 0)