mirror of https://github.com/status-im/reagent.git
341 lines
12 KiB
Clojure
341 lines
12 KiB
Clojure
(ns reagentdemo.news.news050
|
||
(:require [reagent.core :as r]
|
||
[reagentdemo.syntax :as s]
|
||
[sitetools.core :as tools :refer [link]]
|
||
[reagentdemo.common :as common :refer [demo-component]]))
|
||
|
||
(def url "/news/news050.html")
|
||
(def title "News in 0.5.0")
|
||
|
||
(def new-in-alpha nil)
|
||
|
||
(def ns-src (s/syntaxed "(ns example
|
||
(:require [reagent.core :as r]))"))
|
||
|
||
(def cel-link "http://facebook.github.io/react/docs/top-level-api.html#react.createelement")
|
||
|
||
(def figwheel-link "https://github.com/bhauman/lein-figwheel")
|
||
|
||
|
||
|
||
(defonce person (r/atom {:name
|
||
{:first-name "John" :last-name "Smith"}}))
|
||
|
||
(defn input [prompt val]
|
||
[:div
|
||
prompt
|
||
[:input {:value @val
|
||
:on-change #(reset! val (.-target.value %))}]])
|
||
|
||
(defn name-edit [n]
|
||
(let [{:keys [first-name last-name]} @n]
|
||
[:div
|
||
[:p "I'm editing " first-name " " last-name "."]
|
||
|
||
[input "First name: " (r/wrap first-name
|
||
swap! n assoc :first-name)]
|
||
[input "Last name: " (r/wrap last-name
|
||
swap! n assoc :last-name)]]))
|
||
|
||
(defn parent []
|
||
[:div
|
||
[:p "Current state: " (pr-str @person)]
|
||
[name-edit (r/wrap (:name @person)
|
||
swap! person assoc :name)]])
|
||
|
||
(defn cursor-name-edit [n]
|
||
(let [{:keys [first-name last-name]} @n]
|
||
[:div
|
||
[:p "I'm editing " first-name " " last-name "."]
|
||
|
||
[input "First name: " (r/cursor n [:first-name])]
|
||
[input "Last name: " (r/cursor n [:last-name])]]))
|
||
|
||
(defn cursor-parent []
|
||
[:div
|
||
[:p "Current state: " (pr-str @person)]
|
||
[cursor-name-edit (r/cursor person [:name])]])
|
||
|
||
(defn person-get-set
|
||
([k] (get-in @person k))
|
||
([k v] (swap! person assoc-in k v)))
|
||
|
||
(defn get-set-parent []
|
||
[:div
|
||
[:p "Current state: " (pr-str @person)]
|
||
[cursor-name-edit (r/cursor person-get-set [:name])]])
|
||
|
||
(defn integration []
|
||
[:div
|
||
[:div.foo "Hello " [:strong "world"]]
|
||
|
||
(r/create-element "div"
|
||
#js{:className "foo"}
|
||
"Hello "
|
||
(r/create-element "strong"
|
||
#js{}
|
||
"world"))
|
||
|
||
(r/create-element "div"
|
||
#js{:className "foo"}
|
||
"Hello "
|
||
(r/as-element [:strong "world"]))
|
||
|
||
[:div.foo "Hello " (r/create-element "strong"
|
||
#js{}
|
||
"world")]])
|
||
|
||
|
||
(def div-adapter (r/adapt-react-class "div"))
|
||
|
||
(defn adapted []
|
||
[div-adapter {:class "foo"}
|
||
"Hello " [:strong "world"]])
|
||
|
||
|
||
(defn exported [props]
|
||
[:div "Hi, " (:name props)])
|
||
|
||
(def react-comp (r/reactify-component exported))
|
||
|
||
(defn could-be-jsx []
|
||
(r/create-element react-comp #js{:name "world"}))
|
||
|
||
|
||
(defn main [{:keys [summary]}]
|
||
[:div.reagent-demo
|
||
[:h1 [link {:href url} title]]
|
||
[:span "2014-12-10"]
|
||
[:div.demo-text
|
||
[:p "Reagent 0.5.0 has automatic importing of React.js, two kinds
|
||
of cursors, better integration of native React components, better
|
||
performance, easier integration with e.g Figwheel, and more."]
|
||
|
||
(if summary
|
||
[link {:href url
|
||
:class 'news-read-more} "Read more"]
|
||
[:div.demo-text
|
||
[:h2 "A new way of importing React"]
|
||
|
||
[:p new-in-alpha "Reagent now takes advantage of
|
||
ClojureScript’s new way of packaging JavaScript dependencies.
|
||
That means that you no longer have to include React in your
|
||
HTML, nor should you use " [:code ":preamble"] ". Instead,
|
||
Reagent depends on the " [:code "cljsjs/react"] " library."]
|
||
|
||
[:p "If you want to use another version of React, you can do
|
||
that in two ways. In both cases you’ll have to
|
||
exclude " [:code "cljsjs/react"] " by using
|
||
e.g " [:code "[reagent \"0.5.0-alpha3\" :exclusions [cljsjs/react]]"]
|
||
" in the " [:code ":dependencies"] " section of your "
|
||
[:code "project.clj"] "."]
|
||
|
||
[:p "You can then add e.g " [:code "[cljsjs/react-with-addons
|
||
\"0.12.2-4\"]"] " as a dependency. Or you can add a file
|
||
named " [:code "cljsjs/react.cljs"] ", containing
|
||
just " [:code "(ns cljsjs.react)"] ", to your project – and then
|
||
import React in some other way."]
|
||
|
||
[:p "Reagent now requires ClojureScript 0.0-2816 or later."]
|
||
|
||
|
||
[:h2 "Splitting atoms"]
|
||
|
||
[:p "Reagent now has a simple way to make reusable components
|
||
that edit part of a parents state: "]
|
||
|
||
[:p " The new " [:code "wrap"] "
|
||
function combines two parts – a simple value, and a callback
|
||
for when that value should change – into one value, that
|
||
happens to look as an atom. "]
|
||
|
||
[:p "The first argument to " [:code "wrap"] " should be the
|
||
value that will be returned by " [:code "@the-result"] "."]
|
||
|
||
[:p "The second argument should be a function, that will be
|
||
passed the new value whenever the result is changed (with
|
||
optional extra arguments to " [:code "wrap"] " prepended, as
|
||
with " [:code "partial"] ")."]
|
||
|
||
[:p "Usage can look something like this:"]
|
||
|
||
[demo-component {:comp parent
|
||
:src [:pre ns-src
|
||
(s/src-of [:person :input :name-edit
|
||
:parent])]}]
|
||
|
||
[:p "Here, the " [:code "parent"] " component controls the
|
||
global state, and delegates editing the name
|
||
to " [:code "name-edit"] ". " [:code "name-edit"] " in turn
|
||
delegates the actual input of first and last names
|
||
to " [:code "input"] "."]
|
||
|
||
[:p [:b "Note: "] "The result from " [:code "wrap"] " is just a
|
||
simple and light-weight value, that happens to look like an
|
||
atom – it doesn’t by itself trigger any re-renderings
|
||
like " [:code "reagent.core/atom"] " does. That means that it
|
||
is probably only useful to pass from one component to another,
|
||
and that the callback function in the end must cause a ”real”
|
||
atom to change."]
|
||
|
||
[:h2 "Cursors"]
|
||
|
||
[:p new-in-alpha "Reagent has another way of isolating part of
|
||
a data structure in an atom: " [:code "reagent.core/cursor"] ".
|
||
Using the same state as in the previous example, usage now looks
|
||
like this:"]
|
||
|
||
[demo-component {:comp cursor-parent
|
||
:src (s/src-of [:cursor-name-edit
|
||
:cursor-parent])}]
|
||
|
||
[:p new-in-alpha "Cursors can now also be generalized to use
|
||
any transformation of data from and to a source atom (or many
|
||
atoms, for that matter). To use that, you pass a function
|
||
to " [:code "cursor"] " instead of an atom, as in this
|
||
example:"]
|
||
|
||
[demo-component {:comp cursor-parent
|
||
:src (s/src-of [:person-get-set
|
||
:get-set-parent])}]
|
||
|
||
[:p "The function passed to " [:code "cursor"] " will be called
|
||
with one argument to get data (it is passed the key, i.e the
|
||
second argument to " [:code "cursor"] "), and two arguments
|
||
when the cursor is changed (then it is passed the key and the
|
||
new value)."]
|
||
|
||
[:p "The getter function can reference one or many Reagent
|
||
atoms (or other cursors). If the cursor is used in a component
|
||
the getter function will re-run to change the value of the
|
||
cursor just like a Reagent component does."]
|
||
|
||
[:h2 "Values and references"]
|
||
|
||
[:p "So what’s the difference between wraps and cursors? Why
|
||
have both?"]
|
||
|
||
[:p "A " [:code "wrap"] " is just a value that happens to look
|
||
like an " [:code "atom"] ". It doesn’t change unless you tell
|
||
it to. It is a very lightweight combination of value and a
|
||
callback to back-propagate changes to the value. It relies only
|
||
on Reagent’s equality test
|
||
in " [:code ":should-component-update"] " to avoid unnecessary
|
||
re-rendering."]
|
||
|
||
[:p "A " [:code "cursor"] ", on the other hand, will always be
|
||
up-to-date with the value of the source atom. In other words,
|
||
it acts a reference to part of the value of the source.
|
||
Components that " [:code "deref"] " cursors are re-rendered
|
||
automatically, in exactly the same way as if
|
||
they " [:code "deref"] " a normal Reagent atom (unnecessary
|
||
re-rendering is avoided by checking if the cursor's value has
|
||
changed using " [:code "identical?"] ")."]
|
||
|
||
|
||
[:h2 "Faster rendering"]
|
||
|
||
[:p "Reagent used to wrap all ”native” React components in an
|
||
extra Reagent component, in order to keep track of how deep in
|
||
the component tree each component was (to make sure that
|
||
un-necessary re-renderings were avoided)."]
|
||
|
||
[:p "Now, this extra wrapper-component isn’t needed anymore,
|
||
which means quite a bit faster generation of native React
|
||
elements. This will be noticeable if you generate html strings,
|
||
or if you animate a large number of components."]
|
||
|
||
|
||
[:h2 "Simple React integration"]
|
||
|
||
[:p "Since Reagent doesn't need those wrappers anymore it is
|
||
also now easier to mix native React components with Reagent
|
||
ones. There’s a new convenience
|
||
function, " [:code "reagent.core/create-element"] ", that
|
||
simply calls " [:a {:href
|
||
cel-link} [:code "React.createElement"]] ". This,
|
||
unsurprisingly, creates React elements, either from the result
|
||
of " [:code "React.createClass"] " or html tag names."]
|
||
|
||
[:p [:code "reagent.core/as-element"] " turns Reagent’s hiccup
|
||
forms into React elements, that can be passed to ordinary React
|
||
components. The combination of " [:code "create-element"] "
|
||
and " [:code "as-element"] " allows mixing and matching of
|
||
Reagent and React components."]
|
||
|
||
[:p "For an example, here are four different ways to achieve
|
||
the same thing:"]
|
||
|
||
[demo-component {:comp integration
|
||
:src (s/src-of [:integration])}]
|
||
|
||
[:p new-in-alpha "If you don't need/want this kind of low-level
|
||
control over interaction with javascript React, you can also
|
||
use the new function " [:code "adapt-react-class"] ", that will
|
||
take any React class, and turn it into something that can be
|
||
called from Reagent directly. The example from above would then
|
||
become:"]
|
||
|
||
[demo-component {:comp adapted
|
||
:src (s/src-of [:div-adapter :adapted])}]
|
||
|
||
[:p new-in-alpha "You can also do the opposite: call Reagent
|
||
components from JavaScript React (for example from JSX). For
|
||
this purpose, you'd use another adapter
|
||
– " [:code "reactify-component"] " – like this:"]
|
||
|
||
[demo-component {:comp could-be-jsx
|
||
:src (s/src-of [:exported :react-comp
|
||
:could-be-jsx])}]
|
||
|
||
[:p "The " [:code "exported"] " component will be called with a
|
||
single argument: the React " [:code "props"] ", converted to a
|
||
ClojureScript " [:code "map"] "."]
|
||
|
||
|
||
[:h2 "More equality"]
|
||
|
||
[:p "Reagent used to have a rather complicated way of
|
||
determining when a component should be re-rendered in response
|
||
to changing arguments. Now the rule is much simpler: a
|
||
component will be re-rendered if the old and new arguments are
|
||
not equal (i.e. they are compared with a
|
||
simple " [:code "="] ")."]
|
||
|
||
[:p [:strong "Note: "] "This is a breaking change! It means
|
||
that you can no longer pass infinite " [:code "seq"] "s to a
|
||
component."]
|
||
|
||
[:h2 "React 0.12"]
|
||
|
||
[:p "Reagent now comes with, and requires, React 0.12.2. To
|
||
mirror the changes in API in React, some Reagent functions have
|
||
gotten new names: "]
|
||
|
||
[:ul
|
||
[:li [:code "render-component"] " is now " [:code "render"]]
|
||
[:li [:code "render-component-to-string"] " is
|
||
now " [:code "render-to-string"]]
|
||
[:li [:code "as-component"] " is now " [:code "as-element"]]]
|
||
|
||
[:p "The old names still work, though."]
|
||
|
||
[:p "There is also a new
|
||
function, " [:code "render-to-static-markup"] ", that works
|
||
just like render-to-string, except that it doesn’t add
|
||
React-specific attributes."]
|
||
|
||
|
||
[:h2 "Easier live-programming"]
|
||
|
||
[:p "It is now easier than before to integrate Reagent with
|
||
e.g. the rather excellent " [:a {:href
|
||
figwheel-link} "figwheel"] ", since " [:code "render"] " now
|
||
will cause the entire component tree to update (by-passing the
|
||
equality checks)."]
|
||
|
||
[:p new-in-alpha "All the examples in the Reagent repo now uses
|
||
figwheel."]])]])
|
||
|
||
(tools/register-page url [#'main] title)
|