From 3c2c63402e5b4bfabde378787727b48d7be73591 Mon Sep 17 00:00:00 2001 From: Dan Holmsand Date: Sat, 8 Feb 2014 13:55:01 +0100 Subject: [PATCH] Breaking change: Allow arbitrary arguments to component functions Previously, the first argument had to be a map, and the rest of the arguments where passed as a vector as the second argument. Now [my-comp foo...] generally behaves as (my-comp foo...). Since `this` is no longer passed to component functions, add current-component function. Also change signatures of React callbacks, to reflect that arbitrary arguments can be passed. Remove set-props and replace-props for the same reason. Add argv accessor and set-args instead. --- project.clj | 2 +- src/reagent/core.cljs | 29 ++++++----- src/reagent/impl/component.cljs | 91 +++++++++++++++++++-------------- src/reagent/impl/template.cljs | 47 ++++++++--------- src/reagent/impl/util.cljs | 29 +++++------ test/testcloact.cljs | 44 +++++++++------- 6 files changed, 132 insertions(+), 110 deletions(-) diff --git a/project.clj b/project.clj index 046f103..b764293 100644 --- a/project.clj +++ b/project.clj @@ -1,5 +1,5 @@ -(defproject reagent "0.3.0" +(defproject reagent "0.4.0-SNAPSHOT" :url "http://github.com/holmsand/reagent" :license {:name "MIT"} :description "A simple ClojureScript interface to React" diff --git a/src/reagent/core.cljs b/src/reagent/core.cljs index 3ae07ab..874efd1 100644 --- a/src/reagent/core.cljs +++ b/src/reagent/core.cljs @@ -41,14 +41,14 @@ Returns the mounted component instance." "Create a component, React style. Should be called with a map, 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-will-receive-props (fn [this new-argv]) +:should-component-update (fn [this old-argv new-argv]) :component-will-mount (fn [this]) :component-did-mount (fn [this]) -:component-will-update (fn [this new-props new-children]) -:component-did-update (fn [this old-props old-children]) +:component-will-update (fn [this new-argv]) +:component-did-update (fn [this old-argv]) :component-will-unmount (fn [this]) -:render (fn [props children this])} +:render (fn [this])} Everything is optional, except :render. " @@ -56,16 +56,12 @@ Everything is optional, except :render. (comp/create-class spec)) +(defn replace-args [comp new-args] + (assert (vector? new-args)) + (comp/set-args comp new-args)) -(defn set-props - "Merge the props of a mounted, top-level component." - [comp props] - (comp/set-props comp props)) - -(defn replace-props - "Set the props of a mounted, top-level component." - [comp props] - (comp/replace-props comp props)) +(defn current-component [] + comp/*current-component*) (defn state @@ -94,6 +90,11 @@ Everything is optional, except :render. [this] (comp/get-children this)) +(defn argv + "Returns the entire Hiccup form passed to the component." + [this] + (comp/get-argv this)) + (defn dom-node "Returns the root DOM node of a mounted component." [this] diff --git a/src/reagent/impl/component.cljs b/src/reagent/impl/component.cljs index 1493cd4..061c522 100644 --- a/src/reagent/impl/component.cljs +++ b/src/reagent/impl/component.cljs @@ -2,11 +2,12 @@ (ns reagent.impl.component (:refer-clojure :exclude [flush]) (:require [reagent.impl.template :as tmpl - :refer [cljs-props cljs-children cljs-level React]] + :refer [cljs-argv cljs-level React]] [reagent.impl.util :as util] [reagent.ratom :as ratom] [reagent.debug :refer-macros [dbg prn]])) +(declare ^:dynamic *current-component*) (def cljs-state "cljsState") @@ -29,20 +30,29 @@ (defn js-props [C] (aget C "props")) -(defn props-in-props [props] - (aget props cljs-props)) +(defn extract-props [v] + (let [p (get v 1)] + (if (map? p) p))) + +(defn extract-children [v] + (let [first-child (if (-> v (get 1) map?) 2 1)] + (if (> (count v) first-child) + (subvec v first-child)))) + +(defn get-argv [C] + (-> C js-props (aget cljs-argv))) (defn get-props [C] - (-> C js-props props-in-props)) + (-> C get-argv extract-props)) (defn get-children [C] - (-> C js-props (aget cljs-children))) + (-> C get-argv extract-children)) -(defn replace-props [C newprops] - (.setProps C (js-obj cljs-props newprops))) +(defn set-args [C new-args] + (let [argv (get-argv C)] + (.setProps C (js-obj cljs-argv + (into [(argv 0)] new-args))))) -(defn set-props [C newprops] - (replace-props C (merge (get-props C) newprops))) ;;; Rendering @@ -98,17 +108,25 @@ (defn do-render [C f] (set! (.-cljsIsDirty C) false) - (let [p (js-props C) - props (props-in-props p) - children (aget p cljs-children) - ;; Call render function with props, children, component - res (f props children C) - conv (if (vector? res) - (tmpl/as-component res (aget p cljs-level)) - (if (fn? res) - (do-render C (set! (.-cljsRenderFn C) res)) - res))] - conv)) + (binding [*current-component* C] + (let [p (js-props C) + argv (aget p cljs-argv) + n (count argv) + res (if (nil? (aget C "componentFunction")) + (f C) + (case n + 1 (f) + 2 (f (argv 1)) + 3 (f (argv 1) (argv 2)) + 4 (f (argv 1) (argv 2) (argv 3)) + 5 (f (argv 1) (argv 2) (argv 3) (argv 4)) + (apply f (subvec argv 1)))) + conv (if (vector? res) + (tmpl/as-component res (aget p cljs-level)) + (if (fn? res) + (do-render C (set! (.-cljsRenderFn C) res)) + res))] + conv))) (defn render [C] (assert C) @@ -136,33 +154,28 @@ :componentWillReceiveProps (fn [C props] - (when f (f C (props-in-props props)))) + (when f (f C (aget props cljs-argv)))) :shouldComponentUpdate (fn [C nextprops nextstate] ;; Don't care about nextstate here, we use forceUpdate ;; when only when state has changed anyway. (let [inprops (js-props C) - p1 (aget inprops cljs-props) - c1 (aget inprops cljs-children) - p2 (aget nextprops cljs-props) - c2 (aget nextprops cljs-children)] + old-argv (aget inprops cljs-argv) + new-argv (aget nextprops cljs-argv)] (if (nil? f) - (not (util/equal-args p1 c1 p2 c2)) - ;; call f with oldprops newprops oldchildren newchildren - (f C p1 p2 c1 c2)))) + (not (util/equal-args old-argv new-argv)) + (f C old-argv new-argv)))) :componentWillUpdate (fn [C nextprops] - (let [p (aget nextprops cljs-props) - c (aget nextprops cljs-children)] - (f C p c))) + (let [next-argv (aget nextprops cljs-argv)] + (f C next-argv))) :componentDidUpdate (fn [C oldprops] - (let [p (aget oldprops cljs-props) - c (aget oldprops cljs-children)] - (f C p c))) + (let [old-argv (aget oldprops cljs-argv)] + (f C old-argv))) :componentWillUnmount (fn [C] @@ -202,11 +215,15 @@ (defn wrap-funs [fun-map] (let [name (or (:displayName fun-map) - (when-let [r (:render fun-map)] + (when-let [r (or (:componentFunction fun-map) + (:render fun-map))] (or (.-displayName r) (.-name r)))) - name1 (if (empty? name) (str (gensym "reagent")) name)] - (into {} (for [[k v] (assoc fun-map :displayName name1)] + name1 (if (empty? name) (str (gensym "reagent")) name) + fmap (if-let [cf (:componentFunction fun-map)] + (assoc fun-map :render cf) + fun-map)] + (into {} (for [[k v] (assoc fmap :displayName name1)] [k (get-wrapper k v name1)])))) (defn cljsify [body] diff --git a/src/reagent/impl/template.cljs b/src/reagent/impl/template.cljs index 012b00e..e17ece3 100644 --- a/src/reagent/impl/template.cljs +++ b/src/reagent/impl/template.cljs @@ -7,8 +7,7 @@ (def React reactimport/React) -(def cljs-props "cljsProps") -(def cljs-children "cljsChildren") +(def cljs-argv "cljsArgv") (def cljs-level "cljsLevel") (def isClient (not (nil? (try (.-document js/window) @@ -88,8 +87,12 @@ (def input-components #{(aget DOM "input") (aget DOM "textarea")}) +(defn extract-props [v] + (let [p (get v 1)] + (if (map? p) p))) + (defn get-props [this] - (-> this (aget "props") (aget cljs-props))) + (-> this (aget "props") (aget cljs-argv) extract-props)) (defn input-initial-state [this] (let [props (get-props this)] @@ -106,7 +109,7 @@ (on-change e)))) (defn input-will-receive-props [this new-props] - (let [props (aget new-props cljs-props)] + (let [props (-> new-props (aget cljs-argv) extract-props)] (.setState this #js {:value (:value props) :checked (:checked props)}))) @@ -119,12 +122,16 @@ (defn wrapped-render [this comp id-class] (let [inprops (aget this "props") - props (aget inprops cljs-props) + argv (aget inprops cljs-argv) level (aget inprops cljs-level) + props (get argv 1) hasprops (or (nil? props) (map? props)) - jsargs (->> (aget inprops cljs-children) - (map-into-array as-component (inc level))) - jsprops (convert-props props id-class)] + first-child (if hasprops 2 1) + jsargs (if (> (count argv) first-child) + (map-into-array as-component (inc level) + (subvec argv first-child)) + (array)) + jsprops (convert-props (if hasprops props) id-class)] (when (input-components comp) (input-render-setup this jsprops)) (.unshift jsargs jsprops) @@ -132,11 +139,9 @@ (defn wrapped-should-update [C nextprops nextstate] (let [inprops (aget C "props") - p1 (aget inprops cljs-props) - c1 (aget inprops cljs-children) - p2 (aget nextprops cljs-props) - c2 (aget nextprops cljs-children)] - (not (util/equal-args p1 c1 p2 c2)))) + a1 (aget inprops cljs-argv) + a2 (aget nextprops cljs-argv)] + (not (util/equal-args a1 a2)))) (defn wrap-component [comp extras name] (let [def #js {:render @@ -175,7 +180,7 @@ (defn fn-to-class [f] (let [spec (meta f) - withrender (merge spec {:render f}) + withrender (assoc spec :component-function f) res (reagent.core/create-class withrender) wrapf (.-cljsReactClass res)] (set! (.-cljsReactClass f) wrapf) @@ -195,15 +200,11 @@ (defn vec-to-comp [v level] (assert (pos? (count v))) - (let [[tag props] v - hasmap (map? props) - first-child (if (or hasmap (nil? props)) 2 1) - c (as-class tag) - jsprops (js-obj cljs-props (if hasmap props) - cljs-children (if (> (count v) first-child) - (subvec v first-child)) - cljs-level level)] - (when hasmap + (let [props (get v 1) + c (as-class (v 0)) + jsprops (js-obj cljs-argv v + cljs-level level)] + (when (map? props) (let [key (:key props)] (when-not (nil? key) (aset jsprops "key" key)))) diff --git a/src/reagent/impl/util.cljs b/src/reagent/impl/util.cljs index 8e582b6..fc8b103 100644 --- a/src/reagent/impl/util.cljs +++ b/src/reagent/impl/util.cljs @@ -35,18 +35,6 @@ (assert (map? p1)) (merge-style p1 (merge-class p1 (merge p1 p2)))))) -(defn identical-parts [v1 v2] - ;; Compare two vectors using identical? - (or (identical? v1 v2) - (let [end (count v1)] - (and (== end (count v2)) - (loop [n 0] - (if (>= n end) - true - (if (identical? (nth v1 n) (nth v2 n)) - (recur (inc n)) - false))))))) - (def -not-found (js-obj)) (defn shallow-equal-maps [x y] @@ -70,7 +58,16 @@ (reduced false)))) true x)))) -(defn equal-args [p1 c1 p2 c2] - [p1 c1 p2 c2] - (and (identical-parts c1 c2) - (shallow-equal-maps p1 p2))) +(defn equal-args [v1 v2] + ;; Compare two vectors using identical? + (or (identical? v1 v2) + (let [end (count v1)] + (and (== end (count v2)) + (loop [n 1] + (if (>= n end) + true + (if (or (identical? (v1 n) (v2 n)) + (and (== 1 n) + (shallow-equal-maps (v1 1) (v2 1)))) + (recur (inc n)) + false))))))) diff --git a/test/testcloact.cljs b/test/testcloact.cljs index 2dd6f22..ee2cfdc 100644 --- a/test/testcloact.cljs +++ b/test/testcloact.cljs @@ -51,25 +51,28 @@ (when isClient (let [ran (atom 0) comp (reagent/create-class - {:component-did-mount #(swap! ran inc) - :render (fn [props children this] - (assert (map? props)) - (swap! ran inc) - [:div (str "hi " (:foo props) ".")])})] + {:component-did-mount #(swap! ran inc) + :render + (fn [this] + (let [props (reagent/props this)] + (assert (map? props)) + (assert (= props ((reagent/argv this) 1))) + (swap! ran inc) + [:div (str "hi " (:foo props) ".")]))})] (with-mounted-component (comp {:foo "you"}) (fn [C div] (swap! ran inc) (is (found-in #"hi you" div)) - - (reagent/set-props C {:foo "there"}) + + (reagent/replace-args C [{:foo "there"}]) (is (found-in #"hi there" div)) (let [runs @ran] - (reagent/set-props C {:foo "there"}) + (reagent/replace-args C [{:foo "there"}]) (is (found-in #"hi there" div)) (is (= runs @ran))) - (reagent/replace-props C {:foobar "not used"}) + (reagent/replace-args C [{:foobar "not used"}]) (is (found-in #"hi ." div)))) (is (= 5 @ran))))) @@ -77,10 +80,12 @@ (when isClient (let [ran (atom 0) comp (reagent/create-class - {:get-initial-state (fn []) - :render (fn [props children this] - (swap! ran inc) - [:div (str "hi " (:foo (reagent/state this)))])})] + {:get-initial-state (fn []) + :render + (fn [] + (let [this (reagent/current-component)] + (swap! ran inc) + [:div (str "hi " (:foo (reagent/state this)))]))})] (with-mounted-component (comp) (fn [C div] (swap! ran inc) @@ -164,12 +169,13 @@ (deftest init-state-test (when isClient (let [ran (atom 0) - really-simple (fn [props children this] - (swap! ran inc) - (reagent/set-state this {:foo "foobar"}) - (fn [] - [:div (str "this is " - (:foo (reagent/state this)))]))] + really-simple (fn [] + (let [this (reagent/current-component)] + (swap! ran inc) + (reagent/set-state this {:foo "foobar"}) + (fn [] + [:div (str "this is " + (:foo (reagent/state this)))])))] (with-mounted-component [really-simple nil nil] (fn [c div] (swap! ran inc)