Merge pull request #325 from reagent-project/fix-array-ops

Fix aget/aset use with objects
This commit is contained in:
Juho Teperi 2018-12-31 15:34:41 +02:00 committed by GitHub
commit 88e9833be9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 294 additions and 358 deletions

View File

@ -11,6 +11,12 @@ ensure `:class` is merged correctly when it is defined as collection. ([#412](ht
- Add `reagent.core/class-names` utility functions which can be used
to normalize and combine `:class` values (similar to `classnames` JS library)
- Fix comparing Reagent `PartialFn` to `nil` ([#385](https://github.com/reagent-project/reagent/issues/385))
- Reagent no longer abuses `aget` or `aset` for accessing objects, and instead
uses correct Object interop forms, allowing use of ClojureScript `:checked-arrays :warn` option.
- **Removed `reagent.interop` namespace**
- These macros where bad practice and don't work properly if
React code is optimized by Closure. Proper object interop forms or `goog.object` functions
should be used instead.
## 0.8.1 (2018-05-15)

View File

@ -2,8 +2,7 @@
(:require [clojure.string :as string]
[goog.events :as evt]
[reagent.core :as r]
[reagent.debug :refer-macros [dbg log dev?]]
[reagent.interop :as i :refer-macros [$ $!]])
[reagent.debug :refer-macros [dbg log dev?]])
(:import goog.History
[goog.history Html5History EventType]))

View File

@ -217,8 +217,6 @@ In the code sample above, notice that the renderer function is identified via an
Its a trap to mistakenly use `:render` because you won't get any errors, **except** the function you supply will only ever be called with one parameter, and it won't be the one you expect. [Some details here](https://github.com/reagent-project/reagent/issues/47#issuecomment-61056999).
**Note:** prior to version 0.5.0 you had to use the key `:component-function` instead of `:reagent-render`.
**Rookie mistake**
While you can override `component-should-update` to achieve some performance improvements, you probably shouldn't unless you really, really know what you are doing. Resist the urge. Your current performance is just fine. :-)

View File

@ -12,7 +12,7 @@
[cljsjs/react-dom-server "16.6.0-0"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-doo "0.1.10"]
[lein-doo "0.1.11"]
[lein-codox "0.10.3"]
[lein-figwheel "0.5.18"]]
@ -24,7 +24,7 @@
:profiles {:dev {:dependencies [[org.clojure/clojurescript "1.10.439"]
[figwheel "0.5.18"]
[doo "0.1.10"]
[doo "0.1.11"]
[cljsjs/prop-types "15.6.2-0"]]
:source-paths ["demo" "test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src"]
:resource-paths ["site" "target/cljsbuild/client" "target/cljsbuild/client-npm"]}}
@ -53,7 +53,8 @@
:output-dir "target/cljsbuild/client/public/js/out"
:output-to "target/cljsbuild/client/public/js/main.js"
:npm-deps false
:asset-path "js/out"}}
:asset-path "js/out"
:checked-arrays :warn}}
{:id "client-npm"
:source-paths ["demo"]
@ -65,7 +66,8 @@
:output-dir "target/cljsbuild/client-npm/public/js/out"
:output-to "target/cljsbuild/client-npm/public/js/main.js"
:npm-deps true
:asset-path "js/out"}}
:asset-path "js/out"
:checked-arrays :warn}}
{:id "test"
:source-paths ["test"]
@ -76,7 +78,8 @@
:output-dir "target/cljsbuild/test/out"
:output-to "target/cljsbuild/test/main.js"
:npm-deps false
:aot-cache true}}
:aot-cache true
:checked-arrays :warn}}
{:id "test-npm"
:source-paths ["test"]
@ -87,7 +90,8 @@
:output-dir "target/cljsbuild/test-npm/out"
:output-to "target/cljsbuild/test-npm/main.js"
:npm-deps true
:aot-cache true}}
:aot-cache true
:checked-arrays :warn}}
;; Separate source-path as this namespace uses Node built-in modules which
;; aren't available for other targets, and would break other builds.
@ -110,7 +114,8 @@
:output-dir "target/cljsbuild/node-test/out"
:output-to "target/cljsbuild/node-test/main.js"
:npm-deps false
:aot-cache true}}
:aot-cache true
:checked-arrays :warn}}
{:id "node-test-npm"
:source-paths ["test/reagenttest/runtests.cljs"]
@ -122,7 +127,8 @@
:output-dir "target/cljsbuild/node-test-npm/out"
:output-to "target/cljsbuild/node-test-npm/main.js"
:npm-deps true
:aot-cache true}}
:aot-cache true
:checked-arrays :warn}}
;; With :advanched source-paths doesn't matter that much as
;; Cljs compiler will only read :main file.
@ -162,7 +168,8 @@
:output-dir "target/cljsbuild/prod-test/out"
:closure-warnings {:global-this :off}
:npm-deps false
:aot-cache true}}
:aot-cache true
:checked-arrays :warn}}
{:id "prod-test-npm"
:source-paths ["test"]
@ -175,4 +182,5 @@
:output-dir "target/cljsbuild/prod-test-npm/out"
:closure-warnings {:global-this :off}
:npm-deps true
:aot-cache true}}]})
:aot-cache true
:checked-arrays :warn}}]})

View File

@ -11,7 +11,6 @@
assert-some assert-component
assert-js-object assert-new-state
assert-callable]]
[reagent.interop :refer-macros [$ $!]]
[reagent.dom :as dom]))
(def is-client util/is-client)
@ -104,8 +103,7 @@
(batch/flush-after-render))
(defn create-class
"Create a component, React style. Should be called with a map,
looking like this:
"Creates JS class based on provided Clojure map, for example:
```cljs
{:get-initial-state (fn [this])
@ -119,7 +117,13 @@
:reagent-render (fn [args....])} ;; or :render (fn [this])
```
Everything is optional, except either :reagent-render or :render."
Everything is optional, except either :reagent-render or :render.
Map keys should use `React.Component` method names (https://reactjs.org/docs/react-component.html),
and can be provided in snake-case or camelCase.
Constructor function is defined using key `:get-initial-state`.
React built-in static methods or properties are automatically defined as statics."
[spec]
(comp/create-class spec))

View File

@ -4,8 +4,7 @@
[reagent.impl.template :as tmpl]
[reagent.impl.batching :as batch]
[reagent.ratom :as ratom]
[reagent.debug :refer-macros [dbg]]
[reagent.interop :refer-macros [$ $!]]))
[reagent.debug :refer-macros [dbg]]))
(defonce ^:private imported nil)

View File

@ -2,8 +2,7 @@
(:require ["react-dom/server" :as dom-server]
[reagent.impl.util :as util]
[reagent.impl.template :as tmpl]
[reagent.ratom :as ratom]
[reagent.interop :refer-macros [$ $!]]))
[reagent.ratom :as ratom]))
(defonce ^:private imported nil)

View File

@ -1,9 +1,7 @@
(ns reagent.impl.batching
(:refer-clojure :exclude [flush])
(:require [reagent.debug :refer-macros [dbg assert-some]]
[reagent.interop :refer-macros [$ $!]]
[reagent.impl.util :refer [is-client]]
[clojure.string :as string]))
(:require [reagent.debug :refer-macros [assert-some]]
[reagent.impl.util :refer [is-client]]))
;;; Update batching
@ -19,15 +17,15 @@
(if-not is-client
fake-raf
(let [w js/window]
(or ($ w :requestAnimationFrame)
($ w :webkitRequestAnimationFrame)
($ w :mozRequestAnimationFrame)
($ w :msRequestAnimationFrame)
(or (.-requestAnimationFrame w)
(.-webkitRequestAnimationFrame w)
(.-mozRequestAnimationFrame w)
(.-msRequestAnimationFrame w)
fake-raf))))
(defn compare-mount-order [c1 c2]
(- ($ c1 :cljsMountOrder)
($ c2 :cljsMountOrder)))
(- (.-cljsMountOrder c1)
(.-cljsMountOrder c2)))
(defn run-queue [a]
;; sort components by mount order, to make sure parents
@ -35,55 +33,67 @@
(.sort a compare-mount-order)
(dotimes [i (alength a)]
(let [c (aget a i)]
(when (true? ($ c :cljsIsDirty))
($ c forceUpdate)))))
(when (true? (.-cljsIsDirty c))
(.forceUpdate c)))))
;; Set from ratom.cljs
(defonce ratom-flush (fn []))
(defn run-funs [fs]
(dotimes [i (alength fs)]
((aget fs i))))
(defn enqueue [queue fs f]
(assert-some f "Enqueued function")
(.push fs f)
(.schedule queue))
(deftype RenderQueue [^:mutable ^boolean scheduled?]
Object
(enqueue [this k f]
(assert-some f "Enqueued function")
(when (nil? (aget this k))
(aset this k (array)))
(.push (aget this k) f)
(.schedule this))
(run-funs [this k]
(when-some [fs (aget this k)]
(aset this k nil)
(dotimes [i (alength fs)]
((aget fs i)))))
(schedule [this]
(when-not scheduled?
(set! scheduled? true)
(next-tick #(.run-queues this))))
(queue-render [this c]
(.enqueue this "componentQueue" c))
(when (nil? (.-componentQueue this))
(set! (.-componentQueue this) (array)))
(enqueue this (.-componentQueue this) c))
(add-before-flush [this f]
(.enqueue this "beforeFlush" f))
(when (nil? (.-beforeFlush this))
(set! (.-beforeFlush this) (array)))
(enqueue this (.-beforeFlush this) f))
(add-after-render [this f]
(.enqueue this "afterRender" f))
(when (nil? (.-afterRender this))
(set! (.-afterRender this) (array)))
(enqueue this (.-afterRender this) f))
(run-queues [this]
(set! scheduled? false)
(.flush-queues this))
(flush-before-flush [this]
(when-some [fs (.-beforeFlush this)]
(set! (.-beforeFlush this) nil)
(run-funs fs)))
(flush-render [this]
(when-some [fs (.-componentQueue this)]
(set! (.-componentQueue this) nil)
(run-queue fs)))
(flush-after-render [this]
(.run-funs this "afterRender"))
(when-some [fs (.-afterRender this)]
(set! (.-afterRender this) nil)
(run-funs fs)))
(flush-queues [this]
(.run-funs this "beforeFlush")
(.flush-before-flush this)
(ratom-flush)
(when-some [cs (aget this "componentQueue")]
(aset this "componentQueue" nil)
(run-queue cs))
(.flush-render this)
(.flush-after-render this)))
(defonce render-queue (->RenderQueue false))
@ -95,12 +105,12 @@
(.flush-after-render render-queue))
(defn queue-render [c]
(when-not ($ c :cljsIsDirty)
($! c :cljsIsDirty true)
(when-not (.-cljsIsDirty c)
(set! (.-cljsIsDirty c) true)
(.queue-render render-queue c)))
(defn mark-rendered [c]
($! c :cljsIsDirty false))
(set! (.-cljsIsDirty c) false))
(defn do-before-flush [f]
(.add-before-flush render-queue f))

View File

@ -4,9 +4,9 @@
[reagent.impl.util :as util]
[reagent.impl.batching :as batch]
[reagent.ratom :as ratom]
[reagent.interop :refer-macros [$ $!]]
[reagent.debug :refer-macros [dbg prn dev? warn error warn-unless
assert-callable]]))
assert-callable]]
[goog.object :as gobj]))
(declare ^:dynamic *current-component*)
@ -16,10 +16,12 @@
(defn shallow-obj-to-map [o]
(let [ks (js-keys o)
len (alength ks)]
(loop [m {} i 0]
(loop [m {}
i 0]
(if (< i len)
(let [k (aget ks i)]
(recur (assoc m (keyword k) (aget o k)) (inc i)))
(recur (assoc m (keyword k) (gobj/get o k))
(inc i)))
m))))
(defn extract-props [v]
@ -33,52 +35,52 @@
(subvec v first-child))))
(defn props-argv [c p]
(if-some [a ($ p :argv)]
(if-some [a (.-argv p)]
a
[(.-constructor c) (shallow-obj-to-map p)]))
(defn get-argv [c]
(props-argv c ($ c :props)))
(props-argv c (.-props c)))
(defn get-props [c]
(let [p ($ c :props)]
(if-some [v ($ p :argv)]
(let [p (.-props c)]
(if-some [v (.-argv p)]
(extract-props v)
(shallow-obj-to-map p))))
(defn get-children [c]
(let [p ($ c :props)]
(if-some [v ($ p :argv)]
(let [p (.-props c)]
(if-some [v (.-argv p)]
(extract-children v)
(->> ($ p :children)
(->> (.-children p)
(react/Children.toArray)
(into [])))))
(defn ^boolean reagent-class? [c]
(and (fn? c)
(some? (some-> c .-prototype ($ :reagentRender)))))
(some? (some-> c (.-prototype) (.-reagentRender)))))
(defn ^boolean react-class? [c]
(and (fn? c)
(some? (some-> c .-prototype ($ :render)))))
(some? (some-> c (.-prototype) (.-render)))))
(defn ^boolean reagent-component? [c]
(some? ($ c :reagentRender)))
(some? (.-reagentRender c)))
(defn cached-react-class [c]
($ c :cljsReactClass))
(.-cljsReactClass c))
(defn cache-react-class [c constructor]
($! c :cljsReactClass constructor))
(set! (.-cljsReactClass c) constructor))
;;; State
(defn state-atom [this]
(let [sa ($ this :cljsState)]
(let [sa (.-cljsState this)]
(if-not (nil? sa)
sa
($! this :cljsState (ratom/atom nil)))))
(set! (.-cljsState this) (ratom/atom nil)))))
;; avoid circular dependency: this gets set from template.cljs
(defonce as-element nil)
@ -94,9 +96,12 @@
and calls wrap-render again (`recur`), until the render result doesn't evaluate to a function.
3) Anything else - Returns the result of evaluating `c`"
[c]
(let [f ($ c :reagentRender)
(let [f (.-reagentRender c)
_ (assert-callable f)
res (if (true? ($ c :cljsLegacyRender))
;; cljsLegacyRender tells if this calls was defined
;; using :render instead of :reagent-render
;; in that case, the :render fn is called with just `this` as argument.
res (if (true? (.-cljsLegacyRender c))
(.call f c c)
(let [v (get-argv c)
n (count v)]
@ -113,7 +118,7 @@
(fn [& args]
(as-element (apply vector res args)))
res)]
($! c :reagentRender f)
(set! (.-reagentRender c) f)
(recur c))
:else res)))
@ -142,9 +147,10 @@
(def static-fns
{:render
(fn render []
;; TODO: Use static property for cljsRatom
(this-as c (if util/*non-reactive*
(do-render c)
(let [rat ($ c :cljsRatom)]
(let [rat (gobj/get c "cljsRatom")]
(batch/mark-rendered c)
(if (nil? rat)
(ratom/run-in-reaction #(do-render c) c "cljsRatom"
@ -171,8 +177,8 @@
(this-as c
;; Don't care about nextstate here, we use forceUpdate
;; when only when state has changed anyway.
(let [old-argv ($ c :props.argv)
new-argv ($ nextprops :argv)
(let [old-argv (.. c -props -argv)
new-argv (.-argv nextprops)
noargv (or (nil? old-argv) (nil? new-argv))]
(cond
(nil? f) (or noargv (try (not= old-argv new-argv)
@ -193,7 +199,7 @@
:componentWillMount
(fn componentWillMount []
(this-as c
($! c :cljsMountOrder (batch/next-mount-count))
(set! (.-cljsMountOrder c) (batch/next-mount-count))
(when-not (nil? f)
(.call f c c))))
@ -204,8 +210,7 @@
:componentWillUnmount
(fn componentWillUnmount []
(this-as c
(some-> ($ c :cljsRatom)
ratom/dispose!)
(some-> (gobj/get c "cljsRatom") ratom/dispose!)
(batch/mark-rendered c)
(when-not (nil? f)
(.call f c c))))
@ -216,12 +221,14 @@
nil))
(defn get-wrapper [key f name]
(defn get-wrapper [key f]
(let [wrap (custom-wrapper key f)]
(when (and wrap f)
(assert-callable f))
(or wrap f)))
;; Though the value is nil here, the wrapper function will be
;; added to class to manage Reagent ratom lifecycle.
(def obligatory {:shouldComponentUpdate nil
:componentWillMount nil
:componentWillUnmount nil})
@ -238,23 +245,20 @@
(defn wrap-funs [fmap]
(when (dev?)
(let [renders (select-keys fmap [:render :reagentRender :componentFunction])
(let [renders (select-keys fmap [:render :reagentRender])
render-fun (-> renders vals first)]
(assert (not (:componentFunction fmap)) ":component-function is no longer supported, use :reagent-render instead.")
(assert (pos? (count renders)) "Missing reagent-render")
(assert (== 1 (count renders)) "Too many render functions supplied")
(assert-callable render-fun)))
(let [render-fun (or (:reagentRender fmap)
(:componentFunction fmap))
legacy-render (nil? render-fun)
render-fun (or render-fun
(:render fmap))
name (str (or (:displayName fmap)
(util/fun-name render-fun)))
name (case name
"" (str (gensym "reagent"))
name)
legacy-render (nil? (:reagentRender fmap))
name (or (:displayName fmap)
(util/fun-name render-fun)
(str (gensym "reagent")))
fmap (reduce-kv (fn [m k v]
(assoc m k (get-wrapper k v name)))
(assoc m k (get-wrapper k v)))
{} fmap)]
(assoc fmap
:displayName name
@ -265,7 +269,7 @@
(defn map-to-js [m]
(reduce-kv (fn [o k v]
(doto o
(aset (name k) v)))
(gobj/set (name k) v)))
#js{} m))
(defn cljsify [body]
@ -285,14 +289,17 @@
(defn create-class
"Creates JS class based on provided Clojure map.
Map keys should use `React.Component` method names (https://reactjs.org/docs/react-component.html).
Map keys should use `React.Component` method names (https://reactjs.org/docs/react-component.html),
and can be provided in snake-case or camelCase.
Constructor function is defined using key `:getInitialState`.
React built-in static methods or properties are automatically defined as statics."
[body]
{:pre [(map? body)]}
(let [body (cljsify body)
methods (map-to-js (apply dissoc body :displayName :getInitialState built-in-static-method-names))
methods (map-to-js (apply dissoc body :displayName :getInitialState
:render :reagentRender
built-in-static-method-names))
static-methods (map-to-js (select-keys body built-in-static-method-names))
display-name (:displayName body)
construct (:getInitialState body)
@ -302,7 +309,20 @@
(when construct
(construct this))
this))]
(gobj/extend (.-prototype cmp) (.-prototype react/Component) methods)
;; These names SHOULD be mangled by Closure so we can't use goog/extend
(when (:render body)
(set! (.-render (.-prototype cmp)) (:render body)))
(when (:reagentRender body)
(set! (.-reagentRender (.-prototype cmp)) (:reagentRender body)))
(when (:cljsLegacyRender body)
(set! (.-cljsLegacyRender (.-prototype cmp)) (:cljsLegacyRender body)))
(gobj/extend cmp react/Component static-methods)
(when display-name
@ -319,10 +339,10 @@
(defn fiber-component-path [fiber]
(let [name (some-> fiber
($ :type)
($ :displayName))
(.-type)
(.-displayName))
parent (some-> fiber
($ :return))
(.-return))
path (some-> parent
fiber-component-path
(str " > "))
@ -332,18 +352,18 @@
(defn component-path [c]
;; Alternative branch for React 16
;; Try both original name (for UMD foreign-lib) and manged name (property access, for Closure optimized React)
(if-let [fiber (or (some-> c ($ :_reactInternalFiber))
(if-let [fiber (or (some-> c (gobj/get "_reactInternalFiber"))
(some-> c (.-_reactInternalFiber)))]
(fiber-component-path fiber)
(let [instance (or (some-> c ($ :_reactInternalInstance))
(let [instance (or (some-> c (gobj/get "_reactInternalInstance"))
(some-> c (.-_reactInternalInstance))
c)
elem (or (some-> instance ($ :_currentElement))
elem (or (some-> instance (gobj/get "_currentElement"))
(some-> instance (.-_currentElement)))
name (some-> elem
($ :type)
($ :displayName))
owner (or (some-> elem ($ :_owner))
(.-type)
(.-displayName))
owner (or (some-> elem (gobj/get "_owner"))
(some-> elem (.-_owner)))
path (some-> owner
component-path
@ -367,8 +387,8 @@
(not (reagent-class? f))))
"Using native React classes directly in Hiccup forms "
"is not supported. Use create-element or "
"adapt-react-class instead: " (let [n (util/fun-name f)]
(if (empty? n) f n))
"adapt-react-class instead: " (or (util/fun-name f)
f)
(comp-name))
(if (reagent-class? f)
(cache-react-class f f)

View File

@ -6,9 +6,9 @@
[reagent.impl.component :as comp]
[reagent.impl.batching :as batch]
[reagent.ratom :as ratom]
[reagent.interop :refer-macros [$ $!]]
[reagent.debug :refer-macros [dbg prn println log dev?
warn warn-unless]]))
warn warn-unless]]
[goog.object :as gobj]))
(declare as-element)
@ -17,7 +17,7 @@
from a tag name."}
re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
(deftype NativeWrapper [])
(deftype NativeWrapper [tag id className])
;;; Common utilities
@ -40,14 +40,15 @@
(defn cache-get [o k]
(when ^boolean (.hasOwnProperty o k)
(aget o k)))
(gobj/get o k)))
(defn cached-prop-name [k]
(if (named? k)
(if-some [k' (cache-get prop-name-cache (name k))]
k'
(aset prop-name-cache (name k)
(util/dash-to-camel k)))
(let [v (util/dash-to-camel k)]
(gobj/set prop-name-cache (name k))
v))
k))
(defn ^boolean js-val? [x]
@ -57,8 +58,7 @@
(defn kv-conv [o k v]
(doto o
(aset (cached-prop-name k)
(convert-prop-value v))))
(gobj/set (cached-prop-name k) (convert-prop-value v))))
(defn convert-prop-value [x]
(cond (js-val? x) x
@ -78,14 +78,14 @@
(if (named? k)
(if-some [k' (cache-get custom-prop-name-cache (name k))]
k'
(aset custom-prop-name-cache (name k)
(util/dash-to-camel k)))
(let [v (util/dash-to-camel k)]
(gobj/set custom-prop-name-cache (name k) v)
v))
k))
(defn custom-kv-conv [o k v]
(doto o
(aset (cached-custom-prop-name k)
(convert-prop-value v))))
(gobj/set (cached-custom-prop-name k) (convert-prop-value v))))
(defn convert-custom-prop-value [x]
(cond (js-val? x) x
@ -96,19 +96,12 @@
(apply x args))
:else (clj->js x)))
(defn oset [o k v]
(doto (if (nil? o) #js{} o)
(aset k v)))
(defn oget [o k]
(if (nil? o) nil (aget o k)))
(defn set-id-class
"Takes the id and class from tag keyword, and adds them to the
other props. Parsed tag is JS object with :id and :class properties."
[props id-class]
(let [id ($ id-class :id)
class ($ id-class :class)]
(let [id (.-id id-class)
class (.-className id-class)]
(cond-> props
;; Only use ID from tag keyword if no :id in props already
(and (some? id)
@ -124,7 +117,7 @@
props (-> props
(cond-> class (assoc :class (util/class-names class)))
(set-id-class id-class))]
(if ($ id-class :custom)
(if (.-custom id-class)
(convert-custom-prop-value props)
(convert-prop-value props))))
@ -147,14 +140,14 @@
(defn input-node-set-value
[node rendered-value dom-value component {:keys [on-write]}]
(if-not (and (identical? node ($ js/document :activeElement))
(has-selection-api? ($ node :type))
(if-not (and (identical? node (.-activeElement js/document))
(has-selection-api? (.-type node))
(string? rendered-value)
(string? dom-value))
;; just set the value, no need to worry about a cursor
(do
($! component :cljsDOMValue rendered-value)
($! node :value rendered-value)
(set! (.-cljsDOMValue component) rendered-value)
(set! (.-value node) rendered-value)
(when (fn? on-write)
(on-write rendered-value)))
@ -179,37 +172,37 @@
;; So this is just a warning. The code below is simple
;; enough, but if you are tempted to change it, be aware of
;; all the scenarios you have handle.
(let [node-value ($ node :value)]
(let [node-value (.-value node)]
(if (not= node-value dom-value)
;; IE has not notified us of the change yet, so check again later
(batch/do-after-render #(input-component-set-value component))
(let [existing-offset-from-end (- (count node-value)
($ node :selectionStart))
(.-selectionStart node))
new-cursor-offset (- (count rendered-value)
existing-offset-from-end)]
($! component :cljsDOMValue rendered-value)
($! node :value rendered-value)
(set! (.-cljsDOMValue component) rendered-value)
(set! (.-value node) rendered-value)
(when (fn? on-write)
(on-write rendered-value))
($! node :selectionStart new-cursor-offset)
($! node :selectionEnd new-cursor-offset))))))
(set! (.-selectionStart node) new-cursor-offset)
(set! (.-selectionEnd node) new-cursor-offset))))))
(defn input-component-set-value [this]
(when ($ this :cljsInputLive)
($! this :cljsInputDirty false)
(let [rendered-value ($ this :cljsRenderedValue)
dom-value ($ this :cljsDOMValue)
(when (.-cljsInputLive this)
(set! (.-cljsInputDirty this) false)
(let [rendered-value (.-cljsRenderedValue this)
dom-value (.-cljsDOMValue this)
;; Default to the root node within this component
node (find-dom-node this)]
(when (not= rendered-value dom-value)
(input-node-set-value node rendered-value dom-value this {})))))
(defn input-handle-change [this on-change e]
($! this :cljsDOMValue (-> e .-target .-value))
(set! (.-cljsDOMValue this) (-> e .-target .-value))
;; Make sure the input is re-rendered, in case on-change
;; wants to keep the value unchanged
(when-not ($ this :cljsInputDirty)
($! this :cljsInputDirty true)
(when-not (.-cljsInputDirty this)
(set! (.-cljsInputDirty this) true)
(batch/do-after-render #(input-component-set-value this)))
(on-change e))
@ -222,21 +215,20 @@
(.hasOwnProperty jsprops "value"))
(assert find-dom-node
"reagent.dom needs to be loaded for controlled input to work")
(let [v ($ jsprops :value)
(let [v (.-value jsprops)
value (if (nil? v) "" v)
on-change ($ jsprops :onChange)]
(when-not ($ this :cljsInputLive)
on-change (.-onChange jsprops)]
(when-not (.-cljsInputLive this)
;; set initial value
($! this :cljsInputLive true)
($! this :cljsDOMValue value))
($! this :cljsRenderedValue value)
(set! (.-cljsInputLive this) true)
(set! (.-cljsDOMValue this) value))
(set! (.-cljsRenderedValue this) value)
(js-delete jsprops "value")
(doto jsprops
($! :defaultValue value)
($! :onChange #(input-handle-change this on-change %))))))
(set! (.-defaultValue jsprops) value)
(set! (.-onChange jsprops) #(input-handle-change this on-change %)))))
(defn input-unmount [this]
($! this :cljsInputLive nil))
(set! (.-cljsInputLive this) nil))
(defn ^boolean input-component? [x]
(case x
@ -252,10 +244,10 @@
:component-did-update input-component-set-value
:component-will-unmount input-unmount
:reagent-render
(fn [argv comp jsprops first-child]
(fn [argv component jsprops first-child]
(let [this comp/*current-component*]
(input-render-setup this jsprops)
(make-element argv comp jsprops first-child)))})
(make-element argv component jsprops first-child)))})
(defn reagent-input
[]
@ -266,18 +258,19 @@
;;; Conversion from Hiccup forms
(deftype HiccupTag [tag id className custom])
(defn parse-tag [hiccup-tag]
(let [[tag id class] (->> hiccup-tag name (re-matches re-tag) next)
class (when-not (nil? class)
(string/replace class #"\." " "))]
(assert tag (str "Invalid tag: '" hiccup-tag "'"
(comp/comp-name)))
#js {:name tag
:id id
:class class
;; Custom element names must contain hyphen
;; https://www.w3.org/TR/custom-elements/#custom-elements-core-concepts
:custom (not= -1 (.indexOf tag "-"))}))
(let [[tag id className] (->> hiccup-tag name (re-matches re-tag) next)
className (when-not (nil? className)
(string/replace className #"\." " "))]
(assert tag (str "Invalid tag: '" hiccup-tag "'" (comp/comp-name)))
(->HiccupTag tag
id
className
;; Custom element names must contain hyphen
;; https://www.w3.org/TR/custom-elements/#custom-elements-core-concepts
(not= -1 (.indexOf tag "-")))))
(defn try-get-key [x]
;; try catch to avoid clojurescript peculiarity with
@ -296,50 +289,50 @@
(defn reag-element [tag v]
(let [c (comp/as-class tag)
jsprops #js{:argv v}]
jsprops #js {}]
(set! (.-argv jsprops) v)
(when-some [key (key-from-vec v)]
($! jsprops :key key))
(set! (.-key jsprops) key))
(react/createElement c jsprops)))
(defn fragment-element [argv]
(let [props (nth argv 1 nil)
hasprops (or (nil? props) (map? props))
jsprops (convert-prop-value (if hasprops props))
jsprops (if-some [key (key-from-vec argv)]
(oset jsprops "key" key)
jsprops)
jsprops (or (convert-prop-value (if hasprops props))
#js {})
first-child (+ 1 (if hasprops 1 0))]
(when-some [key (key-from-vec argv)]
(set! (.-key jsprops) key))
(make-element argv react/Fragment jsprops first-child)))
(defn adapt-react-class
[c]
(doto (->NativeWrapper)
($! :name c)
($! :id nil)
($! :class nil)))
(->NativeWrapper c nil nil))
(def tag-name-cache #js{})
(defn cached-parse [x]
(if-some [s (cache-get tag-name-cache x)]
s
(aset tag-name-cache x (parse-tag x))))
(let [v (parse-tag x)]
(gobj/set tag-name-cache x v)
v)))
(defn native-element [parsed argv first]
(let [comp ($ parsed :name)
(let [component (.-tag parsed)
props (nth argv first nil)
hasprops (or (nil? props) (map? props))
jsprops (convert-props (if hasprops props) parsed)
jsprops (or (convert-props (if hasprops props) parsed)
#js {})
first-child (+ first (if hasprops 1 0))]
(if (input-component? comp)
(-> [(reagent-input) argv comp jsprops first-child]
(if (input-component? component)
(-> [(reagent-input) argv component jsprops first-child]
(with-meta (meta argv))
as-element)
(let [key (-> (meta argv) get-key)
p (if (nil? key)
jsprops
(oset jsprops "key" key))]
(make-element argv comp p first-child)))))
(do
(when-some [key (-> (meta argv) get-key)]
(set! (.-key jsprops) key))
(make-element argv component jsprops first-child)))))
(defn str-coll [coll]
(if (dev?)
@ -366,11 +359,10 @@
pos (.indexOf n ">")]
(case pos
-1 (native-element (cached-parse n) v 1)
;; TODO: Doesn't this match :>foo or any keyword starting with >
0 (let [comp (nth v 1 nil)]
;; Support [:> comp ...]
0 (let [component (nth v 1 nil)]
;; Support [:> component ...]
(assert (= ">" n) (hiccup-err v "Invalid Hiccup tag"))
(native-element #js{:name comp} v 2))
(native-element (->HiccupTag component nil nil nil) v 2))
;; 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
@ -400,20 +392,15 @@
(set! comp/as-element as-element)
(defn expand-seq [s]
(let [a (into-array s)]
(dotimes [i (alength a)]
(aset a i (as-element (aget a i))))
a))
(into-array (map as-element s)))
(defn expand-seq-dev [s o]
(let [a (into-array s)]
(dotimes [i (alength a)]
(let [val (aget a i)]
(when (and (vector? val)
(nil? (key-from-vec val)))
($! o :no-key true))
(aset a i (as-element val))))
a))
(into-array (map (fn [val]
(when (and (vector? val)
(nil? (key-from-vec val)))
(set! (.-no-key o) true))
(as-element val))
s)))
(defn expand-seq-check [x]
(let [ctx #js{}
@ -421,7 +408,7 @@
(when derefed
(warn (hiccup-err x "Reactive deref not supported in lazy seq, "
"it should be wrapped in doall")))
(when ($ ctx :no-key)
(when (.-no-key ctx)
(warn (hiccup-err x "Every element in a seq should have a unique :key")))
res))
@ -452,12 +439,12 @@
;; ;; "_store" (js-obj)
;; )))
(defn make-element [argv comp jsprops first-child]
(defn make-element [argv component jsprops first-child]
(case (- (count argv) first-child)
;; Optimize cases of zero or one child
0 (react/createElement comp jsprops)
0 (react/createElement component jsprops)
1 (react/createElement comp jsprops
1 (react/createElement component jsprops
(as-element (nth argv first-child nil)))
(.apply react/createElement nil
@ -465,4 +452,4 @@
(when (>= k first-child)
(.push a (as-element v)))
a)
#js[comp jsprops] argv))))
#js[component jsprops] argv))))

View File

@ -1,10 +1,9 @@
(ns reagent.impl.util
(:require [reagent.debug :refer-macros [dbg log warn]]
[reagent.interop :refer-macros [$ $!]]
[clojure.string :as string]))
(def is-client (and (exists? js/window)
(-> js/window ($ :document) nil? not)))
(-> (.-document js/window) nil? not)))
(def ^:dynamic ^boolean *non-reactive* false)
@ -40,16 +39,17 @@
(defn fun-name [f]
(let [n (or (and (fn? f)
(or ($ f :displayName)
($ f :name)))
(or (.-displayName f)
(let [n (.-name f)]
(if (and (string? n) (seq n))
n))))
(and (implements? INamed f)
(name f))
(let [m (meta f)]
(if (map? m)
(:name m))))]
(-> n
str
(clojure.string/replace "$" "."))))
(if n
(string/replace (str n) "$" "."))))
(deftype PartialFn [pfn f args]
Fn
@ -174,5 +174,5 @@
(defn force-update [comp deep]
(if deep
(binding [*always-update* true]
($ comp forceUpdate))
($ comp forceUpdate)))
(.forceUpdate comp))
(.forceUpdate comp)))

View File

@ -1,56 +0,0 @@
(ns reagent.interop
(:require [clojure.string :as string :refer [join]]))
(defn- js-call [f args]
(let [argstr (->> (repeat (count args) "~{}")
(join ","))]
(list* 'js* (str "~{}(" argstr ")") f args)))
(defn- dot-args [object member]
(assert (or (symbol? member)
(keyword? member))
(str "Symbol or keyword expected, not " (pr-str member)))
(assert (or (not (symbol? object))
(not (re-find #"\." (name object))))
(str "Dot not allowed in " object))
(let [n (name member)
field? (or (keyword? member)
(= (subs n 0 1) "-"))
names (-> (if (symbol? member)
(string/replace n #"^-" "")
n)
(string/split #"\."))]
[field? names]))
(defmacro $
"Access member in a javascript object, in a Closure-safe way.
'member' is assumed to be a field if it is a keyword or if
the name starts with '-', otherwise the named function is
called with the optional args.
'member' may contain '.', to allow access in nested objects.
If 'object' is a symbol it is not allowed contain '.'.
($ o :foo) is equivalent to (.-foo o), except that it gives
the same result under advanced compilation.
($ o foo arg1 arg2) is the same as (.foo o arg1 arg2)."
[object member & args]
(let [[field names] (dot-args object member)]
(if field
(do
(assert (empty? args)
(str "Passing args to field doesn't make sense: " member))
`(aget ~object ~@names))
(js-call (list* 'aget object names) args))))
(defmacro $!
"Set field in a javascript object, in a Closure-safe way.
'field' should be a keyword or a symbol starting with '-'.
'field' may contain '.', to allow access in nested objects.
If 'object' is a symbol it is not allowed contain '.'.
($! o :foo 1) is equivalent to (set! (.-foo o) 1), except that it
gives the same result under advanced compilation."
[object field value]
(let [[field names] (dot-args object field)]
(assert field (str "Field name must start with - in " field))
`(aset ~object ~@names ~value)))

View File

@ -1,2 +0,0 @@
(ns reagent.interop
(:require-macros [reagent.interop]))

View File

@ -14,6 +14,25 @@
(deref co#)
co#))
; taken from cljs.core
; https://github.com/binaryage/cljs-oops/issues/14
(defmacro unchecked-aget
([array idx]
(list 'js* "(~{}[~{}])" array idx))
([array idx & idxs]
(let [astr (apply str (repeat (count idxs) "[~{}]"))]
`(~'js* ~(str "(~{}[~{}]" astr ")") ~array ~idx ~@idxs))))
; taken from cljs.core
; https://github.com/binaryage/cljs-oops/issues/14
(defmacro unchecked-aset
([array idx val]
(list 'js* "(~{}[~{}] = ~{})" array idx val))
([array idx idx2 & idxv]
(let [n (dec (count idxv))
astr (apply str (repeat n "[~{}]"))]
`(~'js* ~(str "(~{}[~{}][~{}]" astr " = ~{})") ~array ~idx ~idx2 ~@idxv))))
(defmacro with-let [bindings & body]
(assert (vector? bindings)
(str "with-let bindings must be a vector, not "
@ -27,8 +46,8 @@
x
(let [j (quot i 2)]
`(if ~init
(aset ~v ~j ~x)
(aget ~v ~j)))))
(unchecked-aset ~v ~j ~x)
(unchecked-aget ~v ~j)))))
bindings))
[forms destroy] (let [fin (last body)]
(if (and (list? fin)

View File

@ -4,7 +4,8 @@
(:require [reagent.impl.util :as util]
[reagent.debug :refer-macros [dbg log warn error dev? time]]
[reagent.impl.batching :as batch]
[clojure.set :as s]))
[clojure.set :as s]
[goog.object :as obj]))
(declare ^:dynamic *ratom-context*)
(defonce ^boolean debug false)
@ -187,10 +188,8 @@
(declare make-reaction)
(def ^{:private true :const true} cache-key "reagReactionCache")
(defn- cached-reaction [f o k obj destroy]
(let [m (aget o cache-key)
(let [m (.-reagReactionCache o)
m (if (nil? m) {} m)
r (m k nil)]
(cond
@ -199,15 +198,15 @@
:else (let [r (make-reaction
f :on-dispose (fn [x]
(when debug (swap! -running dec))
(as-> (aget o cache-key) _
(as-> (.-reagReactionCache o) _
(dissoc _ k)
(aset o cache-key _))
(set! (.-reagReactionCache o) _))
(when (some? obj)
(set! (.-reaction obj) nil))
(when (some? destroy)
(destroy x))))
v (-deref r)]
(aset o cache-key (assoc m k r))
(set! (.-reagReactionCache o) (assoc m k r))
(when debug (swap! -running inc))
(when (some? obj)
(set! (.-reaction obj) r))
@ -538,7 +537,7 @@
(._set-opts r opts)
(set! (.-f r) f)
(set! (.-auto-run r) #(run obj))
(aset obj key r))
(obj/set obj key r))
res))
(defn check-derefs [f]

View File

@ -11,10 +11,14 @@
(is (= "class"
(tmpl/cached-custom-prop-name :class))))
;; Cljs.test prints better error if check is Cljs function
(defn js-equal? [a b]
(gobj/equals a b))
(deftest convert-props-test
(is (gobj/equals #js {:className "a"}
(tmpl/convert-props {:class "a"} #js {:id nil :custom false})))
(is (gobj/equals #js {:class "a"}
(tmpl/convert-props {:class "a"} #js {:id nil :custom true})))
(is (gobj/equals #js {:className "a b" :id "a"}
(tmpl/convert-props {:class "b"} #js {:id "a" :class "a" :custom false}))))
(is (js-equal? #js {:className "a"}
(tmpl/convert-props {:class "a"} (tmpl/HiccupTag. nil nil nil false))))
(is (js-equal? #js {:class "a"}
(tmpl/convert-props {:class "a"} (tmpl/HiccupTag. nil nil nil true))))
(is (js-equal? #js {:className "a b" :id "a"}
(tmpl/convert-props {:class "b"} (tmpl/HiccupTag. nil "a" "a" false)))))

View File

@ -1,7 +1,6 @@
(ns reagenttest.runtests
(:require [reagenttest.testreagent]
[reagenttest.testcursor]
[reagenttest.testinterop]
[reagenttest.testratom]
[reagenttest.testratomasync]
[reagenttest.testtrack]

View File

@ -1,56 +0,0 @@
(ns reagenttest.testinterop
(:require [cljs.test :as t :refer-macros [is deftest]]
[reagent.debug :refer-macros [dbg]]
[reagent.interop :refer-macros [$ $!]]))
;; (def is-adv (let [o #js{}]
;; (set! (.-somethinglong o) true)
;; (not= (aget (.keys js/Object o) 0) "somethinglong")))
(deftest iterop-quote
(let [o #js{:foo "foo"
:foobar #js{:bar "bar"
:bar-foo "bar-foo"}
:bar-foo "barfoo"}]
(is (= "foo" ($ o :foo)))
(is (= "bar" ($ o :foobar.bar)))
(is (= "barfoo" ($ o :bar-foo)))
(is (= "foo" ($ o -foo)))
(is (= "bar" ($ o -foobar.bar)))
(is (= "bar-foo" ($ o -foobar.bar-foo)))
(is (= "bar-foo" ($ o :foobar.bar-foo)))
($! o :foo "foo1")
(is (= "foo1" ($ o :foo)))
($! o -foo "foo2")
(is (= "foo2" ($ o -foo)))
($! o :foobar.bar "bar1")
(is (= "bar1" ($ o :foobar.bar)))
($! o -foobar.bar "bar1")
(is (= "bar1" ($ o -foobar.bar)))))
(deftest interop-quote-call
(let [o #js{:bar "bar1"
:foo (fn [x]
(this-as this
(str x ($ this :bar))))}
o2 #js{:o o}]
(is (= "ybar1" ($ o foo "y")))
(is (= "xxbar1" ($ o2 o.foo "xx")))
(is (= "abar1" (-> o2
($ :o)
($ foo "a"))))
(is (= "bar1" ($ o foo)))
(is (= "bar1" ($ o2 o.foo)))
($! o :bar "bar2")
(is (= "bar2" ($ o foo)))
(is (= "1bar2" ($ ($ o :foo)
call o 1)))))

View File

@ -3,7 +3,6 @@
[react :as react]
[reagent.ratom :as rv :refer-macros [reaction]]
[reagent.debug :as debug :refer-macros [dbg println log dev?]]
[reagent.interop :refer-macros [$ $!]]
[reagent.core :as r]
[reagent.dom.server :as server]
[reagent.impl.util :as util]
@ -396,7 +395,7 @@
(swap! top-ran inc)
(r/create-class
{:component-did-mount #(swap! ran inc)
:component-function
:reagent-render
(fn [p a]
(is (= 1 a))
(swap! ran inc)
@ -448,8 +447,8 @@
(this-as
this
(r/create-element
"div" #js {:className ($ this :props.className)}
($ this :props.children))))})
"div" #js {:className (.. this -props -className)}
(.. this -props -children))))})
(gobj/extend cmp react/Component)
cmp))
@ -1094,7 +1093,7 @@
(let [old @spy]
(is (nil? (r/after-render
(fn []
(is (= "DIV" ($ @node :tagName)))
(is (= "DIV" (.-tagName @node)))
(swap! spy inc)))))
(is (= old @spy))
(is (= @exp @val))