|
1 | 1 | (ns reagent.core |
2 | | - (:require [reagent.ratom :as ra])) |
| 2 | + (:require [cljs.core :as core] |
| 3 | + [reagent.ratom :as ra])) |
3 | 4 |
|
4 | 5 | (defmacro with-let |
5 | 6 | "Bind variables as with let, except that when used in a component |
|
24 | 25 | [& body] |
25 | 26 | `(reagent.ratom/make-reaction |
26 | 27 | (fn [] ~@body))) |
| 28 | + |
| 29 | +(defn- parse-sig |
| 30 | + "Parse doc-string, attr-map, and other metadata from the defn like arguments list." |
| 31 | + [name fdecl] |
| 32 | + (let [;; doc-string |
| 33 | + [fdecl m] (if (string? (first fdecl)) |
| 34 | + [(next fdecl) {:doc (first fdecl)}] |
| 35 | + [fdecl {}]) |
| 36 | + ;; attr-map |
| 37 | + [fdecl m] (if (map? (first fdecl)) |
| 38 | + [(next fdecl) (conj m (first fdecl))] |
| 39 | + [fdecl m]) |
| 40 | + ;; If single arity, wrap in one item list for next step |
| 41 | + fdecl (if (vector? (first fdecl)) |
| 42 | + (list fdecl) |
| 43 | + fdecl) |
| 44 | + ;; If multi-arity, the last item could be an additional attr-map |
| 45 | + [fdecl m] (if (map? (last fdecl)) |
| 46 | + [(butlast fdecl) (conj m (last fdecl))] |
| 47 | + [fdecl m]) |
| 48 | + m (conj {:arglists (list 'quote (#'cljs.core/sigs fdecl))} m) |
| 49 | + ;; Merge with the meta from the original sym |
| 50 | + m (conj (if (meta name) (meta name) {}) m)] |
| 51 | + [(with-meta name m) fdecl])) |
| 52 | + |
| 53 | +(defmacro defc |
| 54 | + "Create a Reagent function component |
| 55 | +
|
| 56 | + The functions works like other components (defined using regular defn) when |
| 57 | + used inside hiccup elements (`[component]`), but it can't be used like a regular |
| 58 | + function. The created function is a React JS function component, i.e., it |
| 59 | + takes single js-props argument, and the function body is already wrapped to |
| 60 | + use Reagent implementation to work with Ratoms etc." |
| 61 | + {:arglists '([name doc-string? attr-map? [params*] prepost-map? body] |
| 62 | + [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])} |
| 63 | + [sym & fdecl] |
| 64 | + (let [[fname fdecl] (parse-sig sym fdecl)] |
| 65 | + ;; Consider if :arglists should be replaced with [jsprops] or if that should be |
| 66 | + ;; included as one item? |
| 67 | + `(do |
| 68 | + (def ~fname (reagent.impl.component/memo |
| 69 | + (fn ~sym [jsprops#] |
| 70 | + (let [;; It is important that this fn is using the original name, so |
| 71 | + ;; multi-arity definitions can call the other arities. |
| 72 | + render-fn# (fn ~sym ~@fdecl) |
| 73 | + jsprops2# (js/Object.assign (core/js-obj "reagentRender" render-fn#) jsprops#)] |
| 74 | + (reagent.impl.component/functional-render reagent.impl.template/*current-default-compiler* jsprops2#))))) |
| 75 | + (set! (.-reagent-component ~fname) true) |
| 76 | + (set! (.-displayName ~fname) ~(str sym)) |
| 77 | + (js/Object.defineProperty ~fname "name" (core/js-obj "value" ~(str sym) "writable" false))))) |
| 78 | + |
| 79 | +(comment |
| 80 | + (clojure.pprint/pprint (macroexpand-1 '(defc foobar [a b] (+ a b)))) |
| 81 | + (clojure.pprint/pprint (macroexpand-1 '(defc foobar "docstring" ([a] (foobar a nil)) ([a b] (+ a b))))) |
| 82 | + (clojure.pprint/pprint (clojure.walk/macroexpand-all '(defc foobar [a b] (+ a b))))) |
0 commit comments