2014-01-18 10:31:43 +00:00
|
|
|
|
(ns reagentdemo.intro
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(:require [reagent.core :as r]
|
2015-02-09 20:02:31 +00:00
|
|
|
|
[reagentdemo.syntax :as s]
|
2014-11-22 14:57:40 +00:00
|
|
|
|
[reagentdemo.common :as common :refer [demo-component]]
|
2015-02-10 13:18:56 +00:00
|
|
|
|
[simpleexample.core :as simple]
|
|
|
|
|
[todomvc.core :as todo]))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn simple-component []
|
|
|
|
|
[:div
|
|
|
|
|
[:p "I am a component!"]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
[:p.someclass
|
2014-01-18 10:31:43 +00:00
|
|
|
|
"I have " [:strong "bold"]
|
|
|
|
|
[:span {:style {:color "red"}} " and red "] "text."]])
|
|
|
|
|
|
|
|
|
|
(defn simple-parent []
|
|
|
|
|
[:div
|
|
|
|
|
[:p "I include simple-component."]
|
|
|
|
|
[simple-component]])
|
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
(defn hello-component [name]
|
|
|
|
|
[:p "Hello, " name "!"])
|
|
|
|
|
|
|
|
|
|
(defn say-hello []
|
|
|
|
|
[hello-component "world"])
|
|
|
|
|
|
|
|
|
|
(defn lister [items]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:ul
|
2014-02-15 12:50:10 +00:00
|
|
|
|
(for [item items]
|
|
|
|
|
^{:key item} [:li "Item " item])])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn lister-user []
|
|
|
|
|
[:div
|
|
|
|
|
"Here is a list:"
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[lister (range 3)]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(def click-count (r/atom 0))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn counting-component []
|
|
|
|
|
[:div
|
|
|
|
|
"The atom " [:code "click-count"] " has value: "
|
|
|
|
|
@click-count ". "
|
|
|
|
|
[:input {:type "button" :value "Click me!"
|
|
|
|
|
:on-click #(swap! click-count inc)}]])
|
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
(defn atom-input [value]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:input {:type "text"
|
|
|
|
|
:value @value
|
|
|
|
|
:on-change #(reset! value (-> % .-target .-value))}])
|
|
|
|
|
|
|
|
|
|
(defn shared-state []
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(let [val (r/atom "foo")]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
(fn []
|
|
|
|
|
[:div
|
|
|
|
|
[:p "The value is now: " @val]
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:p "Change it here: " [atom-input val]]])))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn timer-component []
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(let [seconds-elapsed (r/atom 0)]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
(fn []
|
|
|
|
|
(js/setTimeout #(swap! seconds-elapsed inc) 1000)
|
|
|
|
|
[:div
|
|
|
|
|
"Seconds Elapsed: " @seconds-elapsed])))
|
|
|
|
|
|
|
|
|
|
(defn render-simple []
|
2017-03-10 15:19:19 +00:00
|
|
|
|
(r/render [simple-component]
|
|
|
|
|
(.-body js/document)))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(def bmi-data (r/atom {:height 180 :weight 80}))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
(defn calc-bmi []
|
|
|
|
|
(let [{:keys [height weight bmi] :as data} @bmi-data
|
|
|
|
|
h (/ height 100)]
|
|
|
|
|
(if (nil? bmi)
|
|
|
|
|
(assoc data :bmi (/ weight (* h h)))
|
|
|
|
|
(assoc data :weight (* bmi h h)))))
|
|
|
|
|
|
|
|
|
|
(defn slider [param value min max]
|
2015-03-11 17:55:46 +00:00
|
|
|
|
[:input {:type "range" :value value :min min :max max
|
|
|
|
|
:style {:width "100%"}
|
|
|
|
|
:on-change (fn [e]
|
2017-11-02 19:38:02 +00:00
|
|
|
|
(swap! bmi-data assoc param (.. e -target -value))
|
2015-03-11 17:55:46 +00:00
|
|
|
|
(when (not= param :bmi)
|
|
|
|
|
(swap! bmi-data assoc :bmi nil)))}])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn bmi-component []
|
2014-02-15 12:50:10 +00:00
|
|
|
|
(let [{:keys [weight height bmi]} (calc-bmi)
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[color diagnose] (cond
|
|
|
|
|
(< bmi 18.5) ["orange" "underweight"]
|
|
|
|
|
(< bmi 25) ["inherit" "normal"]
|
|
|
|
|
(< bmi 30) ["orange" "overweight"]
|
|
|
|
|
:else ["red" "obese"])]
|
|
|
|
|
[:div
|
|
|
|
|
[:h3 "BMI calculator"]
|
|
|
|
|
[:div
|
|
|
|
|
"Height: " (int height) "cm"
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[slider :height height 100 220]]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:div
|
|
|
|
|
"Weight: " (int weight) "kg"
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[slider :weight weight 30 150]]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:div
|
|
|
|
|
"BMI: " (int bmi) " "
|
|
|
|
|
[:span {:style {:color color}} diagnose]
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[slider :bmi bmi 10 50]]]))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-12-07 18:26:20 +00:00
|
|
|
|
(def ns-src (s/syntaxed "(ns example
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(:require [reagent.core :as r]))"))
|
2014-12-07 18:26:20 +00:00
|
|
|
|
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn intro []
|
2014-11-06 19:21:38 +00:00
|
|
|
|
(let [github {:href "https://github.com/reagent-project/reagent"}
|
2014-01-18 10:31:43 +00:00
|
|
|
|
clojurescript {:href "https://github.com/clojure/clojurescript"}
|
2019-06-30 21:55:03 +00:00
|
|
|
|
react {:href "https://reactjs.org/"}
|
2014-02-15 12:50:10 +00:00
|
|
|
|
hiccup {:href "https://github.com/weavejester/hiccup"}
|
2019-06-30 21:55:03 +00:00
|
|
|
|
react-keys {:href "https://reactjs.org/docs/lists-and-keys.html#keys"}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:div.demo-text
|
|
|
|
|
|
|
|
|
|
[:h2 "Introduction to Reagent"]
|
|
|
|
|
|
|
|
|
|
[:p [:a github "Reagent"] " provides a minimalistic interface
|
|
|
|
|
between " [:a clojurescript "ClojureScript"] " and " [:a
|
|
|
|
|
react "React"] ". It allows you to define efficient React
|
|
|
|
|
components using nothing but plain ClojureScript functions and
|
|
|
|
|
data, that describe your UI using a " [:a hiccup "Hiccup"] "-like
|
|
|
|
|
syntax."]
|
|
|
|
|
|
|
|
|
|
[:p "The goal of Reagent is to make it possible to define
|
|
|
|
|
arbitrarily complex UIs using just a couple of basic concepts,
|
2018-06-21 20:19:31 +00:00
|
|
|
|
and to be fast enough by default that you rarely have to think
|
2014-01-18 10:31:43 +00:00
|
|
|
|
about performance."]
|
|
|
|
|
|
|
|
|
|
[:p "A very basic Reagent component may look something like this: "]
|
|
|
|
|
[demo-component {:comp simple-component
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src (s/src-of [:simple-component])}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p "You can build new components using other components as
|
|
|
|
|
building blocks. Like this:"]
|
|
|
|
|
[demo-component {:comp simple-parent
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src (s/src-of [:simple-parent])}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p "Data is passed to child components using plain old Clojure
|
2014-02-15 12:50:10 +00:00
|
|
|
|
data types. Like this:"]
|
|
|
|
|
|
|
|
|
|
[demo-component {:comp say-hello
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src (s/src-of [:hello-component :say-hello])}]
|
2014-02-15 12:50:10 +00:00
|
|
|
|
|
2014-02-21 15:24:04 +00:00
|
|
|
|
[:p [:strong "Note: "]
|
|
|
|
|
"In the example above, " [:code "hello-component"] " might just
|
|
|
|
|
as well have been called as a normal Clojure function instead of
|
|
|
|
|
as a Reagent component, i.e with parenthesis instead of square
|
|
|
|
|
brackets. The only difference would have been performance, since
|
|
|
|
|
”real” Reagent components are only re-rendered when their data
|
|
|
|
|
have changed. More advanced components though (see below) must
|
|
|
|
|
be called with square brackets."]
|
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:p "Here is another example that shows items in a "
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:code "seq"] ":" ]
|
|
|
|
|
|
|
|
|
|
[demo-component {:comp lister-user
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src (s/src-of [:lister :lister-user])}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p [:strong "Note: "]
|
2014-02-21 15:24:04 +00:00
|
|
|
|
"The " [:code "^{:key item}"] " part above isn’t really necessary
|
|
|
|
|
in this simple example, but attaching a unique key to every item
|
|
|
|
|
in a dynamically generated list of components is good practice,
|
|
|
|
|
and helps React to improve performance for large lists. The key
|
|
|
|
|
can be given either (as in this example) as meta-data, or as a "
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:code ":key"] " item in the first argument to a component (if it
|
2019-06-30 21:55:03 +00:00
|
|
|
|
is a map). See React’s " [:a react-keys "documentation"] "
|
2014-02-15 12:50:10 +00:00
|
|
|
|
for more info."]]))
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn managing-state []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Managing state in Reagent"]
|
|
|
|
|
|
|
|
|
|
[:p "The easiest way to manage state in Reagent is to use Reagent’s
|
|
|
|
|
own version of " [:code "atom"] ". It works exactly like the one in
|
|
|
|
|
clojure.core, except that it keeps track of every time it is
|
|
|
|
|
deref’ed. Any component that uses an " [:code "atom"]" is automagically
|
|
|
|
|
re-rendered when its value changes."]
|
|
|
|
|
|
|
|
|
|
[:p "Let’s demonstrate that with a simple example:"]
|
|
|
|
|
[demo-component {:comp counting-component
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src [:pre
|
|
|
|
|
ns-src
|
|
|
|
|
(s/src-of [:click-count :counting-component])]}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p "Sometimes you may want to maintain state locally in a
|
|
|
|
|
component. That is easy to do with an " [:code "atom"] " as well."]
|
|
|
|
|
|
|
|
|
|
[:p "Here is an example of that, where we call "
|
|
|
|
|
[:code "setTimeout"] " every time the component is rendered to
|
|
|
|
|
update a counter:"]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[demo-component {:comp timer-component
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src (s/src-of [:timer-component])}]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:p "The previous example also uses another feature of Reagent: a
|
|
|
|
|
component function can return another function, that is used to do
|
|
|
|
|
the actual rendering. This function is called with the same
|
|
|
|
|
arguments as the first one."]
|
|
|
|
|
|
|
|
|
|
[:p "This allows you to perform some setup of newly created
|
|
|
|
|
components without resorting to React’s lifecycle events."]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-02-16 07:40:52 +00:00
|
|
|
|
[:p "By simply passing an "[:code "atom"]" around you can share
|
|
|
|
|
state management between components, like this:"]
|
|
|
|
|
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[demo-component {:comp shared-state
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src [:pre
|
|
|
|
|
ns-src
|
|
|
|
|
(s/src-of [:atom-input :shared-state])]}]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:p [:strong "Note: "] "Component functions can be called with any
|
2014-02-16 07:40:52 +00:00
|
|
|
|
arguments – as long as they are immutable. You "[:em "could"]" use
|
|
|
|
|
mutable objects as well, but then you have to make sure that the
|
|
|
|
|
component is updated when your data changes. Reagent assumes by
|
|
|
|
|
default that two objects are equal if they are the same object."]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn essential-api []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Essential API"]
|
|
|
|
|
|
|
|
|
|
[:p "Reagent supports most of React’s API, but there is really only
|
|
|
|
|
one entry-point that is necessary for most applications: "
|
2017-03-10 15:19:19 +00:00
|
|
|
|
[:code "reagent.core/render"] "."]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p "It takes two arguments: a component, and a DOM node. For
|
|
|
|
|
example, splashing the very first example all over the page would
|
|
|
|
|
look like this:"]
|
|
|
|
|
|
2014-12-07 18:26:20 +00:00
|
|
|
|
[demo-component {:src [:pre
|
|
|
|
|
ns-src
|
|
|
|
|
(s/src-of [:simple-component :render-simple])]}]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn performance []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Performance"]
|
|
|
|
|
|
|
|
|
|
[:p "React itself is very fast, and so is Reagent. In fact, Reagent
|
|
|
|
|
will be even faster than plain React a lot of the time, thanks to
|
|
|
|
|
optimizations made possible by ClojureScript."]
|
|
|
|
|
|
|
|
|
|
[:p "Mounted components are only re-rendered when their parameters
|
|
|
|
|
have changed. The change could come from a deref’ed "
|
2014-02-15 12:50:10 +00:00
|
|
|
|
[:code "atom"] ", the arguments passed to the component or
|
|
|
|
|
component state."]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-02-17 11:42:08 +00:00
|
|
|
|
[:p "All of these are checked for changes with "
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:code "identical?"] " which is basically only a pointer
|
2014-02-17 11:42:08 +00:00
|
|
|
|
comparison, so the overhead is very low. Maps passed as arguments
|
|
|
|
|
to components are compared the same way: they are considered equal
|
|
|
|
|
if all their entries are identical. This also applies to built-in
|
|
|
|
|
React components like " [:code ":div"] ", " [:code ":p"] ", etc."]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
2014-02-17 11:42:08 +00:00
|
|
|
|
[:p "All this means that you simply won’t have to care about
|
|
|
|
|
performance most of the time. Just define your UI however you like
|
2014-01-18 10:31:43 +00:00
|
|
|
|
– it will be fast enough."]
|
|
|
|
|
|
|
|
|
|
[:p "There are a couple of situations that you might have to care
|
2014-02-16 07:40:52 +00:00
|
|
|
|
about, though. If you give Reagent a big " [:code "seq"] " of
|
2014-01-18 10:31:43 +00:00
|
|
|
|
components to render, you might have to supply all of them with a
|
2014-02-16 07:40:52 +00:00
|
|
|
|
unique " [:code ":key"] " attribute to speed up rendering (see
|
|
|
|
|
above). Also note that anonymous functions are not, in general,
|
|
|
|
|
equal to each other even if they represent the same code and
|
|
|
|
|
closure."]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
[:p "But again, in general you should just trust that React and
|
|
|
|
|
Reagent will be fast enough. This very page is composed of a single
|
|
|
|
|
Reagent component with thousands of child components (every single
|
|
|
|
|
parenthesis etc in the code examples is a separate component), and
|
|
|
|
|
yet the page can be updated many times every second without taxing
|
|
|
|
|
the browser the slightest."]
|
|
|
|
|
|
|
|
|
|
[:p "Incidentally, this page also uses another React trick: the
|
|
|
|
|
entire page is pre-rendered using Node, and "
|
2016-11-17 00:46:31 +00:00
|
|
|
|
[:code "reagent.dom.server/render-to-string"] ". When it is loaded
|
2014-01-18 10:31:43 +00:00
|
|
|
|
into the browser, React automatically attaches event-handlers to
|
|
|
|
|
the already present DOM tree."]])
|
|
|
|
|
|
|
|
|
|
(defn bmi-demo []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Putting it all together"]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[:p "Here is a slightly less contrived example: a simple BMI
|
|
|
|
|
calculator."]
|
|
|
|
|
|
|
|
|
|
[:p "Data is kept in a single " [:code "reagent.core/atom"] ": a map
|
|
|
|
|
with height, weight and BMI as keys."]
|
|
|
|
|
|
|
|
|
|
[demo-component {:comp bmi-component
|
2014-12-07 18:26:20 +00:00
|
|
|
|
:src [:pre
|
|
|
|
|
ns-src
|
|
|
|
|
(s/src-of [:bmi-data :calc-bmi :slider
|
|
|
|
|
:bmi-component])]}]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn complete-simple-demo []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Complete demo"]
|
|
|
|
|
|
|
|
|
|
[:p "Reagent comes with a couple of complete examples, with
|
|
|
|
|
Leiningen project files and everything. Here’s one of them in
|
|
|
|
|
action:"]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-11-22 14:57:40 +00:00
|
|
|
|
[demo-component {:comp simple/simple-example
|
2014-01-18 10:31:43 +00:00
|
|
|
|
:complete true
|
2015-02-10 13:18:56 +00:00
|
|
|
|
:src (s/src-of nil "simpleexample/core.cljs")}]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn todomvc-demo []
|
|
|
|
|
[:div.demo-text
|
|
|
|
|
[:h2 "Todomvc"]
|
|
|
|
|
|
|
|
|
|
[:p "The obligatory todo list looks roughly like this in
|
|
|
|
|
Reagent (cheating a little bit by skipping routing and
|
|
|
|
|
persistence):"]
|
2014-04-01 17:50:28 +00:00
|
|
|
|
|
2014-11-22 14:57:40 +00:00
|
|
|
|
[demo-component {:comp todo/todo-app
|
2014-01-18 10:31:43 +00:00
|
|
|
|
:complete true
|
2015-02-10 13:18:56 +00:00
|
|
|
|
:src (s/src-of nil "todomvc/core.cljs")}]])
|
2014-01-18 10:31:43 +00:00
|
|
|
|
|
|
|
|
|
(defn main []
|
2015-07-31 06:47:28 +00:00
|
|
|
|
(let [show-all (r/atom false)
|
2014-01-20 08:42:16 +00:00
|
|
|
|
head "Reagent: Minimalistic React for ClojureScript"]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
(js/setTimeout #(reset! show-all true) 500)
|
|
|
|
|
(fn []
|
|
|
|
|
[:div.reagent-demo
|
2014-01-20 08:42:16 +00:00
|
|
|
|
[:h1 head]
|
2014-01-18 10:31:43 +00:00
|
|
|
|
[intro]
|
|
|
|
|
[managing-state]
|
|
|
|
|
[essential-api]
|
|
|
|
|
[bmi-demo]
|
|
|
|
|
[performance]
|
|
|
|
|
;; Show heavy examples on load, to make html file smaller
|
|
|
|
|
(when @show-all [complete-simple-demo])
|
|
|
|
|
(when @show-all [todomvc-demo])])))
|