Wrap different React classes in distinct wrappers

That should improve performance a little, and help React's diffing
This commit is contained in:
Dan Holmsand 2014-01-06 16:35:39 +01:00
parent dd3f9b10e1
commit 11a1c62024
2 changed files with 61 additions and 64 deletions

View File

@ -15,23 +15,6 @@
camels (map string/capitalize (rest words))] camels (map string/capitalize (rest words))]
(apply str (first words) camels))) (apply str (first words) camels)))
;; From Weavejester's Hiccup, via pump:
;; https://github.com/weavejester/hiccup/blob/master/src/hiccup/compiler.clj#L32
(def ^{:doc "Regular expression that parses a CSS-style id and class
from a tag name."
:private true}
re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
(def DOM (aget React "DOM"))
(defn parse-tag [tag]
(let [[tag id class] (->> tag name (re-matches re-tag) next)
comp (aget DOM tag)
class' (when class
(string/replace class #"\." " "))]
[comp (when (or id class')
[id class'])]))
(def attr-aliases {"class" "className" (def attr-aliases {"class" "className"
"for" "htmlFor" "for" "htmlFor"
"charset" "charSet"}) "charset" "charSet"})
@ -73,31 +56,82 @@
(set-tag-extra objprops extra)) (set-tag-extra objprops extra))
objprops)))) objprops))))
(defn map-into-array [f coll]
(let [a (into-array coll)
len (alength a)]
(dotimes [i len]
(aset a i (f (aget a i))))
a))
(declare as-component)
(declare wrapper) (defn wrapped-render [this comp extra]
(let [inprops (aget this "props")
args (.-cljsArgs inprops)
[_ scnd] args
hasprops (or (nil? scnd) (map? scnd))
jsprops (convert-props (if hasprops scnd) extra)
jsargs (->> args
(drop (if hasprops 2 1))
(map-into-array as-component))]
(.apply comp nil (.concat (array jsprops) jsargs))))
(defn wrapped-should-update [C nextprops nextstate]
(let [a1 (-> C (aget "props") .-cljsArgs)
a2 (-> nextprops .-cljsArgs)]
(not (util/equal-args a1 a2))))
(defn wrap-component [comp extras]
(let [spec #js {:render #(this-as C (wrapped-render C comp extras))
:shouldComponentUpdate
#(this-as C (wrapped-should-update C %1 %2))}]
(.createClass React spec)))
;; From Weavejester's Hiccup, via pump:
;; https://github.com/weavejester/hiccup/blob/master/src/hiccup/compiler.clj#L32
(def ^{:doc "Regular expression that parses a CSS-style id and class
from a tag name."
:private true}
re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
(def DOM (aget React "DOM"))
(defn parse-tag [tag]
(let [[tag id class] (->> tag name (re-matches re-tag) next)
comp (aget DOM tag)
class' (when class
(string/replace class #"\." " "))]
[comp (when (or id class')
[id class'])]))
(defn get-wrapper [tag]
(let [[comp extra] (parse-tag tag)]
(wrap-component comp extra)))
(def cached-wrapper (memoize get-wrapper))
(defn fn-to-class [f] (defn fn-to-class [f]
(assert (fn? f)) (assert (fn? f))
(let [spec (meta f) (let [spec (meta f)
withrender (merge spec {:render f}) withrender (merge spec {:render f})
res (cloact.core/create-class withrender)] res (cloact.core/create-class withrender)
(set! (.-cljsReactClass f) (.-cljsReactClass res)) wrapf (.-cljsReactClass res)]
res)) (set! (.-cljsReactClass f) wrapf)
wrapf))
(defn as-class [x] (defn as-class [x]
(cond (cond
(keyword? x) wrapper (keyword? x) (cached-wrapper x)
(not (nil? (.-cljsReactClass x))) x (not (nil? (.-cljsReactClass x))) (.-cljsReactClass x)
:else (do (assert (fn? x)) :else (do (assert (fn? x))
(if (.isValidClass React x) (if (.isValidClass React x)
wrapper (set! (.-cljsReactClass x) (wrap-component x nil))
(fn-to-class x))))) (fn-to-class x)))))
(defn vec-to-comp [v] (defn vec-to-comp [v]
(assert (pos? (count v))) (assert (pos? (count v)))
(let [[tag props] v (let [[tag props] v
c (.-cljsReactClass (as-class tag)) c (as-class tag)
obj (js-obj)] obj (js-obj)]
(set! (.-cljsArgs obj) v) (set! (.-cljsArgs obj) v)
(when (map? props) (when (map? props)
@ -106,44 +140,7 @@
(set! (.-key obj) key)))) (set! (.-key obj) key))))
(c obj))) (c obj)))
(defn map-into-array [f coll]
(let [a (into-array coll)
len (alength a)]
(dotimes [i len]
(aset a i (f (aget a i))))
a))
(defn as-component [x] (defn as-component [x]
(cond (vector? x) (vec-to-comp x) (cond (vector? x) (vec-to-comp x)
(seq? x) (map-into-array as-component x) (seq? x) (map-into-array as-component x)
true x)) true x))
(def cached-tag (memoize parse-tag))
(defn render-wrapped [this]
(let [inprops (aget this "props")
args (.-cljsArgs inprops)
[tag scnd] args
hasprops (or (nil? scnd) (map? scnd))
[native extra] (when (keyword? tag) (cached-tag tag))
f (or native tag)
jsprops (convert-props (when hasprops scnd) extra)
jsargs (->> args
(drop (if hasprops 2 1))
(map-into-array as-component))]
(assert (.isValidClass React f))
(assert (nil? (.-cljsReactClass f)))
(.apply f nil (.concat (array jsprops) jsargs))))
(defn should-update-wrapped [C nextprops nextstate]
(let [a1 (-> C (aget "props") .-cljsArgs)
a2 (-> nextprops .-cljsArgs)]
(not (util/equal-args a1 a2))))
(def wrapper
(.createClass React (js-obj "render"
#(this-as C (render-wrapped C))
"shouldComponentUpdate"
#(this-as C (should-update-wrapped C %1 %2)))))
(set! (.-cljsReactClass wrapper) wrapper)

View File

@ -51,10 +51,10 @@
(defn equal-args [v1 v2] (defn equal-args [v1 v2]
;; Compare two "args" vectors, i.e things like [:div {:foo "bar} "baz"], ;; Compare two "args" vectors, i.e things like [:div {:foo "bar} "baz"],
;; using identical? on all individual parts. ;; using identical? on all individual parts.
;; The first bit (e.g the :div is assumed to be identical).
(or (identical? v1 v2) (or (identical? v1 v2)
(let [c1 (count v1)] (let [c1 (count v1)]
(and (= (nth v1 0) (nth v2 0)) ; may be symbol or fn (and (identical? c1 (count v2))
(identical? c1 (count v2))
(if (< c1 2) (if (< c1 2)
true true
(let [props1 (nth v1 1)] (let [props1 (nth v1 1)]