mirror of https://github.com/status-im/reagent.git
Make functional Reagent components optional, using new opts parameter
Render/as-element and other functions now take additional parameter for setting options. Currently only option is :functional-reag-elements?, if set to true, by default Reagent components are created as Functions, which behave similarly to Classes, but allow using Hooks.
This commit is contained in:
parent
63e118d2a0
commit
22a2841b45
|
@ -46,8 +46,8 @@
|
|||
(defn as-element
|
||||
"Turns a vector of Hiccup syntax into a React element. Returns form
|
||||
unchanged if it is not a vector."
|
||||
[form]
|
||||
(tmpl/as-element form))
|
||||
([form] (tmpl/as-element form nil))
|
||||
([form opts] (tmpl/as-element form opts)))
|
||||
|
||||
(defn adapt-react-class
|
||||
"Returns an adapter for a native React class, that may be used
|
||||
|
@ -60,9 +60,10 @@
|
|||
"Returns an adapter for a Reagent component, that may be used from
|
||||
React, for example in JSX. A single argument, props, is passed to
|
||||
the component, converted to a map."
|
||||
[c]
|
||||
(assert-some c "Component")
|
||||
(comp/reactify-component c))
|
||||
([c] (reactify-component c nil))
|
||||
([c opts]
|
||||
(assert-some c "Component")
|
||||
(comp/reactify-component c opts)))
|
||||
|
||||
(defn render
|
||||
"Render a Reagent component into the DOM. The first argument may be
|
||||
|
|
|
@ -34,10 +34,13 @@
|
|||
Returns the mounted component instance."
|
||||
([comp container]
|
||||
(render comp container nil))
|
||||
([comp container callback]
|
||||
([comp container callback-or-opts]
|
||||
(ratom/flush!)
|
||||
(let [f (fn []
|
||||
(tmpl/as-element (if (fn? comp) (comp) comp)))]
|
||||
(let [[opts callback] (if (fn? callback-or-opts)
|
||||
[nil callback-or-opts]
|
||||
[callback-or-opts (:callback callback-or-opts)])
|
||||
f (fn []
|
||||
(tmpl/as-element (if (fn? comp) (comp) comp) opts))]
|
||||
(render-comp f container callback))))
|
||||
|
||||
(defn unmount-component-at-node
|
||||
|
|
|
@ -6,14 +6,18 @@
|
|||
|
||||
(defn render-to-string
|
||||
"Turns a component into an HTML string."
|
||||
[component]
|
||||
(ratom/flush!)
|
||||
(binding [util/*non-reactive* true]
|
||||
(dom-server/renderToString (tmpl/as-element component))))
|
||||
([component]
|
||||
(render-to-string component nil))
|
||||
([component opts]
|
||||
(ratom/flush!)
|
||||
(binding [util/*non-reactive* true]
|
||||
(dom-server/renderToString (tmpl/as-element component opts)))))
|
||||
|
||||
(defn render-to-static-markup
|
||||
"Turns a component into an HTML string, without data-react-id attributes, etc."
|
||||
[component]
|
||||
(ratom/flush!)
|
||||
([component]
|
||||
(render-to-static-markup component nil))
|
||||
([component opts]
|
||||
(ratom/flush!)
|
||||
(binding [util/*non-reactive* true]
|
||||
(dom-server/renderToStaticMarkup (tmpl/as-element component))))
|
||||
(dom-server/renderToStaticMarkup (tmpl/as-element component opts)))))
|
||||
|
|
|
@ -111,10 +111,11 @@
|
|||
5 (.call f c (nth v 1) (nth v 2) (nth v 3) (nth v 4))
|
||||
(.apply f c (.slice (into-array v) 1)))))]
|
||||
(cond
|
||||
(vector? res) (as-element res)
|
||||
;; FIXME: Opts
|
||||
(vector? res) (as-element res nil)
|
||||
(ifn? res) (let [f (if (reagent-class? res)
|
||||
(fn [& args]
|
||||
(as-element (apply vector res args)))
|
||||
(as-element (apply vector res args) nil))
|
||||
res)]
|
||||
(set! (.-reagentRender c) f)
|
||||
(recur c))
|
||||
|
@ -373,7 +374,7 @@
|
|||
|
||||
cmp))
|
||||
|
||||
(defn fn-to-class [f]
|
||||
(defn fn-to-class [f opts]
|
||||
(assert-callable f)
|
||||
(warn-unless (not (and (react-class? f)
|
||||
(not (reagent-class? f))))
|
||||
|
@ -389,15 +390,16 @@
|
|||
res (create-class withrender)]
|
||||
(cache-react-class f res))))
|
||||
|
||||
(defn as-class [tag]
|
||||
(defn as-class [tag opts]
|
||||
;; TODO: Cache per opts
|
||||
(if-some [cached-class (cached-react-class tag)]
|
||||
cached-class
|
||||
(fn-to-class tag)))
|
||||
(fn-to-class tag opts)))
|
||||
|
||||
(defn reactify-component [comp]
|
||||
(defn reactify-component [comp opts]
|
||||
(if (react-class? comp)
|
||||
comp
|
||||
(as-class comp)))
|
||||
(as-class comp opts)))
|
||||
|
||||
(defn functional-wrap-render
|
||||
[c]
|
||||
|
@ -406,10 +408,10 @@
|
|||
argv (.-argv c)
|
||||
res (apply f argv)]
|
||||
(cond
|
||||
(vector? res) (as-element res)
|
||||
(vector? res) (as-element res (.-opts c))
|
||||
(ifn? res) (let [f (if (reagent-class? res)
|
||||
(fn [& args]
|
||||
(as-element (apply vector res args)))
|
||||
(as-element (apply vector res args) (.-opts c)))
|
||||
res)]
|
||||
(set! (.-reagentRender c) f)
|
||||
(recur c))
|
||||
|
@ -501,6 +503,7 @@
|
|||
original Reagent component."
|
||||
[tag]
|
||||
;; TODO: Could be disabled for optimized builds?
|
||||
;; TODO: Need to cache per opts?
|
||||
(or (gobj/get fun-components tag)
|
||||
(let [f (fn [jsprops] (functional-render jsprops))
|
||||
_ (set! (.-displayName f) (util/fun-name tag))
|
||||
|
|
|
@ -246,10 +246,10 @@
|
|||
:component-did-update input-component-set-value
|
||||
:component-will-unmount input-unmount
|
||||
:reagent-render
|
||||
(fn [argv component jsprops first-child]
|
||||
(fn [argv component jsprops first-child opts]
|
||||
(let [this comp/*current-component*]
|
||||
(input-render-setup this jsprops)
|
||||
(make-element argv component jsprops first-child)))})
|
||||
(make-element argv component jsprops first-child opts)))})
|
||||
|
||||
(defn reagent-input
|
||||
[]
|
||||
|
@ -293,13 +293,21 @@
|
|||
(if (= :> (nth v 0 nil))
|
||||
(get-key (nth v 2 nil))))))
|
||||
|
||||
(defn reag-element [tag v]
|
||||
(defn reag-element [tag v opts]
|
||||
(let [c (comp/as-class tag opts)
|
||||
jsprops #js {}]
|
||||
(set! (.-argv jsprops) v)
|
||||
(when-some [key (key-from-vec v)]
|
||||
(set! (.-key jsprops) key))
|
||||
(react/createElement c jsprops)))
|
||||
|
||||
(defn functional-reag-element [tag v opts]
|
||||
(if (or (comp/react-class? tag)
|
||||
;; TODO: Should check others for real comptibility, this fixes tests
|
||||
;; TODO: Drop support for fn + meta for Class component methods?
|
||||
(:should-component-update (meta tag)))
|
||||
;; as-class unncessary later as tag is always class
|
||||
(let [c (comp/as-class tag)
|
||||
(let [c (comp/as-class tag opts)
|
||||
jsprops #js {}]
|
||||
(set! (.-argv jsprops) v)
|
||||
(when-some [key (key-from-vec v)]
|
||||
|
@ -308,6 +316,7 @@
|
|||
(let [jsprops #js {}]
|
||||
(set! (.-reagentRender jsprops) tag)
|
||||
(set! (.-argv jsprops) (subvec v 1))
|
||||
(set! (.-opts jsprops) opts)
|
||||
(when-some [key (key-from-vec v)]
|
||||
(set! (.-key jsprops) key))
|
||||
(react/createElement (comp/funtional-render-fn tag) jsprops))))
|
||||
|
@ -335,7 +344,7 @@
|
|||
(gobj/set tag-name-cache x v)
|
||||
v)))
|
||||
|
||||
(defn native-element [parsed argv first]
|
||||
(defn native-element [parsed argv first opts]
|
||||
(let [component (.-tag parsed)
|
||||
props (nth argv first nil)
|
||||
hasprops (or (nil? props) (map? props))
|
||||
|
@ -343,13 +352,13 @@
|
|||
#js {})
|
||||
first-child (+ first (if hasprops 1 0))]
|
||||
(if (input-component? component)
|
||||
(-> [(reagent-input) argv component jsprops first-child]
|
||||
(-> [(reagent-input) argv component jsprops first-child opts]
|
||||
(with-meta (meta argv))
|
||||
as-element)
|
||||
(as-element opts))
|
||||
(do
|
||||
(when-some [key (-> (meta argv) get-key)]
|
||||
(set! (.-key jsprops) key))
|
||||
(make-element argv component jsprops first-child)))))
|
||||
(make-element argv component jsprops first-child opts)))))
|
||||
|
||||
(defn str-coll [coll]
|
||||
(if (dev?)
|
||||
|
@ -365,7 +374,7 @@
|
|||
(defn hiccup-err [v & msg]
|
||||
(str (apply str msg) ": " (str-coll v) "\n" (comp/comp-name)))
|
||||
|
||||
(defn vec-to-elem [v]
|
||||
(defn vec-to-elem [v opts]
|
||||
(assert (pos? (count v)) (hiccup-err v "Hiccup form should not be empty"))
|
||||
(let [tag (nth v 0 nil)]
|
||||
(assert (valid-tag? tag) (hiccup-err v "Invalid Hiccup form"))
|
||||
|
@ -377,53 +386,56 @@
|
|||
(let [n (name tag)
|
||||
pos (.indexOf n ">")]
|
||||
(case pos
|
||||
-1 (native-element (cached-parse n) v 1)
|
||||
-1 (native-element (cached-parse n) v 1 opts)
|
||||
0 (let [component (nth v 1 nil)]
|
||||
;; Support [:> component ...]
|
||||
(assert (= ">" n) (hiccup-err v "Invalid Hiccup tag"))
|
||||
(native-element (->HiccupTag component nil nil nil) v 2))
|
||||
(native-element (->HiccupTag component nil nil nil) v 2 opts))
|
||||
;; Support extended hiccup syntax, i.e :div.bar>a.foo
|
||||
;; Apply metadata (e.g. :key) to the outermost element.
|
||||
;; Metadata is probably used only with sequeneces, and in that case
|
||||
;; only the key of the outermost element matters.
|
||||
(recur (with-meta [(subs n 0 pos)
|
||||
(assoc (with-meta v nil) 0 (subs n (inc pos)))]
|
||||
(meta v)))))
|
||||
(meta v))
|
||||
opts)))
|
||||
|
||||
(instance? NativeWrapper tag)
|
||||
(native-element tag v 1)
|
||||
(native-element tag v 1 opts)
|
||||
|
||||
:else (reag-element tag v))))
|
||||
:else (if (:functional-reag-elements? opts)
|
||||
(functional-reag-element tag v opts)
|
||||
(reag-element tag v opts)))))
|
||||
|
||||
(declare expand-seq)
|
||||
(declare expand-seq-check)
|
||||
|
||||
(defn as-element [x]
|
||||
(defn as-element [x opts]
|
||||
(cond (js-val? x) x
|
||||
(vector? x) (vec-to-elem x)
|
||||
(vector? x) (vec-to-elem x opts)
|
||||
(seq? x) (if (dev?)
|
||||
(expand-seq-check x)
|
||||
(expand-seq x))
|
||||
(expand-seq-check x opts)
|
||||
(expand-seq x opts))
|
||||
(named? x) (name x)
|
||||
(satisfies? IPrintWithWriter x) (pr-str x)
|
||||
:else x))
|
||||
|
||||
(set! comp/as-element as-element)
|
||||
|
||||
(defn expand-seq [s]
|
||||
(defn expand-seq [s opts]
|
||||
(into-array (map as-element s)))
|
||||
|
||||
(defn expand-seq-dev [s ^clj o]
|
||||
(defn expand-seq-dev [s ^clj o opts]
|
||||
(into-array (map (fn [val]
|
||||
(when (and (vector? val)
|
||||
(nil? (key-from-vec val)))
|
||||
(set! (.-no-key o) true))
|
||||
(as-element val))
|
||||
(as-element val opts))
|
||||
s)))
|
||||
|
||||
(defn expand-seq-check [x]
|
||||
(defn expand-seq-check [x opts]
|
||||
(let [ctx #js{}
|
||||
[res derefed] (ratom/check-derefs #(expand-seq-dev x ctx))]
|
||||
[res derefed] (ratom/check-derefs #(expand-seq-dev x ctx opts))]
|
||||
(when derefed
|
||||
(warn (hiccup-err x "Reactive deref not supported in lazy seq, "
|
||||
"it should be wrapped in doall")))
|
||||
|
@ -431,17 +443,17 @@
|
|||
(warn (hiccup-err x "Every element in a seq should have a unique :key")))
|
||||
res))
|
||||
|
||||
(defn make-element [argv component jsprops first-child]
|
||||
(defn make-element [argv component jsprops first-child opts]
|
||||
(case (- (count argv) first-child)
|
||||
;; Optimize cases of zero or one child
|
||||
0 (react/createElement component jsprops)
|
||||
|
||||
1 (react/createElement component jsprops
|
||||
(as-element (nth argv first-child nil)))
|
||||
(as-element (nth argv first-child nil) opts))
|
||||
|
||||
(.apply react/createElement nil
|
||||
(reduce-kv (fn [a k v]
|
||||
(when (>= k first-child)
|
||||
(.push a (as-element v)))
|
||||
(.push a (as-element v opts)))
|
||||
a)
|
||||
#js[component jsprops] argv))))
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
(js/performance.mark "functional-start")
|
||||
; (simple-benchmark [x [hello-world-component]] (tmpl/vec-to-elem x) 100000)
|
||||
(dotimes [i 100000]
|
||||
(tmpl/vec-to-elem [hello-world-component]))
|
||||
(tmpl/vec-to-elem [hello-world-component] nil))
|
||||
(js/performance.mark "functional-end")
|
||||
(js/performance.measure "functional" "functional-start" "functional-end")
|
||||
|
||||
(js/performance.mark "class-start")
|
||||
; (simple-benchmark [x [^:class-component hello-world-component]] (tmpl/vec-to-elem x) 100000)
|
||||
(dotimes [i 100000]
|
||||
(tmpl/vec-to-elem [^:class-component hello-world-component]))
|
||||
(tmpl/vec-to-elem [^:class-component hello-world-component] nil))
|
||||
(js/performance.mark "class-end")
|
||||
(js/performance.measure "class" "class-start" "class-end")
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,15 +2,18 @@
|
|||
(:require [reagent.core :as r]
|
||||
[reagent.dom :as rdom]))
|
||||
|
||||
(defn with-mounted-component [comp f]
|
||||
(when r/is-client
|
||||
(let [div (.createElement js/document "div")]
|
||||
(try
|
||||
(let [c (rdom/render comp div)]
|
||||
(f c div))
|
||||
(finally
|
||||
(rdom/unmount-component-at-node div)
|
||||
(r/flush))))))
|
||||
(defn with-mounted-component
|
||||
([comp f]
|
||||
(with-mounted-component comp nil f))
|
||||
([comp opts f]
|
||||
(when r/is-client
|
||||
(let [div (.createElement js/document "div")]
|
||||
(try
|
||||
(let [c (rdom/render comp div opts)]
|
||||
(f c div))
|
||||
(finally
|
||||
(rdom/unmount-component-at-node div)
|
||||
(r/flush)))))))
|
||||
|
||||
(defn with-mounted-component-async [comp done f]
|
||||
(when r/is-client
|
||||
|
|
Loading…
Reference in New Issue