mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-23 15:28:09 +00:00
Docs WIP
This commit is contained in:
parent
31ce3903bf
commit
765b116ae6
@ -2,17 +2,13 @@
|
||||
|
||||
Let's understand how re-frame manages application state.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table Of Contents
|
||||
|
||||
#### Table Of Contents
|
||||
|
||||
- [On Data](#on-data)
|
||||
- [The Big Ratom](#the-big-ratom)
|
||||
- [The Benefits Of Data-In-The-One-Place](#the-benefits-of-data-in-the-one-place)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
|
||||
### On Data
|
||||
|
||||
<blockquote class="twitter-tweet" lang="en"><p>Well-formed Data at rest is as close to perfection in programming as it gets. All the crap that had to happen to put it there however...</p>— Fogus (@fogus) <a href="https://twitter.com/fogus/status/454582953067438080">April 11, 2014</a></blockquote>
|
||||
@ -36,7 +32,7 @@ spent your life breaking systems into pieces, organised around behaviour and try
|
||||
to hide state. I still wake up in a sweat some nights thinking about all
|
||||
that Clojure data lying around exposed and passive.
|
||||
|
||||
But, as Fogus reminded us, data at rest is quite perfect.
|
||||
But, as Fogus reminds us, data at rest is quite perfect.
|
||||
|
||||
In re-frame's reference implementation, `app-db` is one of these:
|
||||
```clj
|
||||
@ -62,7 +58,7 @@ Further Notes:
|
||||
be a [datascript](https://github.com/tonsky/datascript database). In fact, any database which
|
||||
can signal you when it changes would do. We'd love! to be using [datascript](https://github.com/tonsky/datascript database) - so damn cool -
|
||||
but we had too much data in our apps. If you were to use it, you'd have to tweak the
|
||||
reference implementation a bit, [perhaps using this inspiration](https://gist.github.com/allgress/11348685).
|
||||
reference implementation a bit and use [Posh](https://github.com/mpdairy/posh).
|
||||
|
||||
|
||||
### The Benefits Of Data-In-The-One-Place
|
@ -1,39 +1,15 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table Of Contents
|
||||
|
||||
- [Initial Code Walk-through](#initial-code-walk-through)
|
||||
- [What Code?](#what-code)
|
||||
- [What Does It Do?](#what-does-it-do)
|
||||
- [Namespace](#namespace)
|
||||
- [Data Schema](#data-schema)
|
||||
- [Events (domino 1)](#events-domino-1)
|
||||
- [dispatch](#dispatch)
|
||||
- [After dispatch](#after-dispatch)
|
||||
- [Event Handlers (domino 2)](#event-handlers-domino-2)
|
||||
- [reg-event-db](#reg-event-db)
|
||||
- [:initialize](#initialize)
|
||||
- [:timer](#timer)
|
||||
- [:time-color-change](#time-color-change)
|
||||
- [Effect Handlers (domino 3)](#effect-handlers-domino-3)
|
||||
- [Subscription Handlers (domino 4)](#subscription-handlers-domino-4)
|
||||
- [View Functions (domino 5)](#view-functions-domino-5)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Initial Code Walk-through
|
||||
|
||||
At this point in your reading, you are armed with:
|
||||
- a high level understanding of the 6 domino process (from re-frame's README)
|
||||
- an understanding of application state (from the previous tutorial)
|
||||
|
||||
|
||||
In this tutorial, **we'll look at re-frame code**. Finally.
|
||||
|
||||
### What Code?
|
||||
|
||||
This repo contains an `/example` application called "simple",
|
||||
which has around 70 lines of code. We'll look at every line
|
||||
and understand what it does.
|
||||
which has around 70 lines of code. We'll look at every line.
|
||||
|
||||
You are currently about 50% the way to understanding re-frame. By the
|
||||
end of this tutorial, you'll be at 70%, which is good
|
||||
@ -70,15 +46,15 @@ for your application state (the data stored in `app-db`). But,
|
||||
here, to minimise cognitive load, we'll cut that corner.
|
||||
|
||||
But ... we can't cut it completely. You'll still need an
|
||||
informal description ... for this app `app-db` will contain
|
||||
informal description, and here it is ... for this app `app-db` will contain
|
||||
a two-key map like this:
|
||||
```cljs
|
||||
{:time (js/Date.) ;; current time for display
|
||||
:time-color "#f88"} ;; what colour should the time be shown in
|
||||
{:time (js/Date.) ;; current time for display
|
||||
:time-color "#f88"} ;; the colour in which the time should be be shown
|
||||
```
|
||||
|
||||
re-frame itself owns/manages `app-db` (see FAQ #1),
|
||||
supplying the value within it (a two-key map in this case)
|
||||
re-frame itself owns/manages `app-db` (see FAQ #1), and it will
|
||||
supply the value within it (a two-key map in this case)
|
||||
to your various handlers as required.
|
||||
|
||||
## Events (domino 1)
|
||||
@ -145,7 +121,7 @@ The `router`:
|
||||
for this kind of event
|
||||
3. calls that event handler with the necessary arguments
|
||||
|
||||
As a re-frame app developer, your job then is to write and register a handler
|
||||
As a re-frame app developer, your job, then, is to write and register a handler
|
||||
for each kind of event.
|
||||
|
||||
## Event Handlers (domino 2)
|
||||
@ -173,7 +149,7 @@ The handler function you provide should expect two parameters:
|
||||
- `db` the current application state
|
||||
- `v` the event vector
|
||||
|
||||
So your function will have a signature like this: `(fn [db v] ...)`.
|
||||
So, your function will have a signature like this: `(fn [db v] ...)`.
|
||||
|
||||
Each event handler must compute and return the new state of
|
||||
the application, which means it normally returns a
|
||||
@ -192,14 +168,15 @@ to be computed. More on this soon.
|
||||
### :initialize
|
||||
|
||||
On startup, application state must be initialised. We
|
||||
want to put a sensible value into `app-db`.
|
||||
want to put a sensible value into `app-db` which will
|
||||
otherwise contain `{}`.
|
||||
|
||||
So a `(dispatch [:initialize])` will happen early in the
|
||||
apps life (more on this below), and we need to write an `event handler`
|
||||
for it.
|
||||
|
||||
Now this event handler is slightly unusual because it doesn't
|
||||
much care what the value in `db` - it just wants to plonk
|
||||
much care about the existing value in `db` - it just wants to plonk
|
||||
in a new complete value.
|
||||
|
||||
Like this:
|
||||
@ -216,7 +193,7 @@ This particular handler `fn` ignores the two parameters
|
||||
a map literal, which becomes the application
|
||||
state.
|
||||
|
||||
Here's an alternative way of writing it:
|
||||
Here's an alternative way of writing it which does pay attention to the existing value of `db`:
|
||||
```clj
|
||||
(rf/reg-event-db
|
||||
:initialize
|
||||
@ -226,8 +203,6 @@ Here's an alternative way of writing it:
|
||||
(assoc :time-color "#f88")))
|
||||
```
|
||||
|
||||
`app-db` starts off holding a `{}` value. So we assume `db` will be `{}`
|
||||
but, irrespective, we just assoc into it.
|
||||
|
||||
### :timer
|
||||
|
||||
@ -286,27 +261,25 @@ source data from other subscriptions. So a tree of dependencies
|
||||
results.
|
||||
|
||||
The Views (Domino 5) are the leaves. The root is `app-db` and the
|
||||
intermediate nodes are computations being performed by
|
||||
|
||||
Each query is identified by an `id` XXXX
|
||||
intermediate nodes are computations being performed by Domino 4 query functions.
|
||||
|
||||
Now, the two examples below are utterly trivial. They just extract part of the application
|
||||
state and return it. So, there's virtually no computation. More interesting
|
||||
state and return it. So, there's virtually no computation. More interesting, layered
|
||||
subscriptions and more explanation can be found in the todomvc example.
|
||||
|
||||
`reg-sub` associates a `query id` with a function which computes
|
||||
that query. It's use looks like this:
|
||||
```clj
|
||||
(reg-sub
|
||||
:some-query-id ;; query identifier
|
||||
some-function) ;; the function which will compute the query
|
||||
:some-query-id ;; query id (used later in subscribe)
|
||||
a-query-fn) ;; the function which will compute the query
|
||||
```
|
||||
If, later, we see a view function requesting data like this:
|
||||
`(listen [:some-query-id])` ;; note use of `:some-query-id` XXX using listen
|
||||
then `some-function` will be used to perform the query over application state.
|
||||
`(subscribe [:some-query-id])` ;; note use of `:some-query-id`
|
||||
then `a-query-fn` will be used to perform the query over application state.
|
||||
|
||||
Each time application state changes, `some-function` will be
|
||||
called again to compute a new materialised view (a new computation)
|
||||
Each time application state changes, `a-query-fn` will be
|
||||
called again to compute a new materialised view (a new computation over app state)
|
||||
and that new value will be given to any view function which is subscribed
|
||||
to `:some-query-id`. The view function itself will then also be called again
|
||||
to compute new DOM (because it depends on a query value which changed).
|
||||
@ -328,7 +301,7 @@ Here's the code:
|
||||
(:time-color db)))
|
||||
```
|
||||
|
||||
Like I said, both of these queries are trivial.
|
||||
Like I said, both of these queries are trivial. See todomvc for more interesting ones.
|
||||
|
||||
## View Functions (domino 5)
|
||||
|
||||
@ -358,13 +331,17 @@ And if we call it:
|
||||
|
||||
Yep, that's a vector with two elements: a keyword and a string.
|
||||
|
||||
Now, greet is pretty simple and there's no "Data In", here, just "Hiccup out".
|
||||
Now,`greet` is pretty simple and there's no "Data In", here, just "Hiccup out".
|
||||
|
||||
### Sourcing data.
|
||||
### Sourcing data
|
||||
|
||||
In order to render a DOM representation of the application state, view functions
|
||||
must first obtain that state. This happens via subscriptions.
|
||||
|
||||
> XXX This particular document is a WIP ... it peters out after this ... I wouldn't read any more.
|
||||
|
||||
-----
|
||||
|
||||
transform data into data. They source
|
||||
data from subscriptions (queries across application state), and
|
||||
the data they return is hiccup-formatted, which is a proxy for DOM.
|
||||
@ -459,5 +436,4 @@ the synchronous
|
||||
using standard, supplied ones.
|
||||
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
|
||||
- write Reagent view functions (view layer) (domino 5)
|
||||
|
||||
re-frame's job is to XXX
|
||||
|
@ -32,7 +32,7 @@ and get you reading and writing code ASAP.
|
||||
|
||||
**But** there are other interesting perspectives on re-frame
|
||||
which will deepen your understanding of its design,
|
||||
and enable you to get the best from it.
|
||||
and help you to get the best from it.
|
||||
|
||||
This tutorial is a tour
|
||||
of these ideas, justifications and insights. It is a little rambling, but I
|
||||
@ -48,7 +48,8 @@ For all its considerable brilliance, Reagent (+ React)
|
||||
delivers only the 'V' part of a traditional MVC framework.
|
||||
|
||||
But apps involve much more than V. We build quite complicated
|
||||
SPAs which can run to 50K lines of code. So, where does the control logic go? How is state stored & manipulated? etc.
|
||||
SPAs which can run to 50K lines of code. So, I wanted to know:
|
||||
where does the control logic go? How is state stored & manipulated? etc.
|
||||
|
||||
We read up on [Pedestal App], [Flux],
|
||||
[Hoplon], [Om], early [Elm], etc and re-frame is the architecture that
|
||||
@ -78,8 +79,7 @@ __Second__, we believe in ClojureScript, immutable data and the process of build
|
||||
a system out of pure functions.
|
||||
|
||||
__Third__, we believe in the primacy of data, for the reasons described in
|
||||
the main README. re-frame has a data oriented functional architecture. It
|
||||
implements an infinite loop of Derived data.
|
||||
the main README. re-frame has a data oriented, functional architecture.
|
||||
|
||||
__Fourth__, we believe that Reactive Programming is one honking good idea.
|
||||
How did we ever live without it? It is a quite beautiful solution to one half of re-frame's
|
||||
@ -122,7 +122,7 @@ The only way the app "moves forwards" is via events. "Replaying events" moves yo
|
||||
step by step towards the error causing problem.
|
||||
|
||||
This is perfect for debugging assuming, of course, you are in a position to capture
|
||||
an app state checkpoint, and the events since then.
|
||||
a checkpoint of `app-db`, and the events since then.
|
||||
|
||||
Here's Martin Fowler's [description of Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html).
|
||||
|
||||
@ -167,8 +167,8 @@ allows:
|
||||
2. Event handlers sometimes need coeffects (arguments) in addition to `db` and `v`.
|
||||
|
||||
But, even if it isn't the full picture, it is a very useful
|
||||
and interesting mental model. We first saw it in Elm's early use
|
||||
of `foldp` (fold from the past), which was later enshrined in the
|
||||
and interesting mental model. We were first exposed to this idea
|
||||
via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
|
||||
Elm Architecture.
|
||||
|
||||
And for the love of all that is good, please watch this terrific
|
||||
@ -178,21 +178,21 @@ all the problems that evaporate.
|
||||
Think about that: shared mutable state (the root of all evil),
|
||||
re-imagined as a stream!! Blew my socks off.
|
||||
|
||||
If, by chance, you have watched that video, you might twig to
|
||||
the idea that `app-db` is really a derived value .. the video talks
|
||||
If, by chance, you ever watched that video (you should!), you might then twig to
|
||||
the idea that `app-db` is really a derived value ... the video talks
|
||||
a lot about derived values. So, yes, app-db is a derived value of the `perpetual reduce`.
|
||||
|
||||
And yet, it acts as the authoritative source of state in the app. And yet,
|
||||
it isn't, it is simply a piece of derived state. And yet, it is the source.
|
||||
it isn't, it is simply a piece of derived state. And yet, it is the source. Etc.
|
||||
|
||||
This is an infinite loop of sorts. An infinite loop of derived data.
|
||||
This is an infinite loop of sorts - an infinite loop of derived data.
|
||||
|
||||
## It does FSM
|
||||
|
||||
> Any sufficiently complicated GUI contains an ad hoc,
|
||||
> informally-specified, bug-ridden, slow implementation
|
||||
> of a hierarchical Finite State Machine <br>
|
||||
> -- [my 11th rule](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule)
|
||||
> -- [me failing to impress my two twitter followers]
|
||||
|
||||
`event handlers` collectively
|
||||
implement the "control" part of an application. Their logic
|
||||
@ -233,13 +233,14 @@ Events - that's the way we roll.
|
||||
|
||||
My job is to be a relentless cheerleader for re-frame, right?
|
||||
The gyrations of my Pom-Poms should be tectonic,
|
||||
but the following quote just makes me smile. It should
|
||||
but the following quote makes me smile. It should
|
||||
be taught in all ComSci courses.
|
||||
|
||||
> We begin in admiration and end by organizing our disappointment <br>
|
||||
> -- Gaston Bachelard (French philosopher)
|
||||
|
||||
Of course, that only applies if you get passionate about a technology.
|
||||
Of course, that only applies if you get passionate about a technology
|
||||
(a flaw of mine).
|
||||
|
||||
But, no. No! Those French Philosophers and their pessimism - ignore him!!
|
||||
Your love for re-frame will be deep, abiding and enriching.
|
||||
@ -259,63 +260,15 @@ Using data gives us:
|
||||
- logability and event sourcing
|
||||
- a more flexible version of "partial" (curring)
|
||||
|
||||
## Derived Data
|
||||
|
||||
**Derived data is flowing around the
|
||||
loop, reactively, through pure functions.** There is a pause in the loop whenever we wait
|
||||
for a new event, but the moment we get it, it's another iteration of the "derived data" FRP loop.
|
||||
|
||||
Derived values, all the way down, forever.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Prefer Dumb Views - Part 1
|
||||
|
||||
Many events are dispatched by the DOM in response to user actions.
|
||||
|
||||
For example, a button view might be like this:
|
||||
```clj
|
||||
(defn yes-button
|
||||
[]
|
||||
[:div {:class "button-class"
|
||||
:on-click #(dispatch [:yes-button-clicked])}
|
||||
"Yes"])
|
||||
```
|
||||
|
||||
Notice that `on-click` DOM handler:
|
||||
```clj
|
||||
#(dispatch [:yes-button-clicked])
|
||||
```
|
||||
|
||||
With re-frame, we want the DOM as passive as possible. We do
|
||||
not want our views containing any imperative control logic.
|
||||
All of that should be computed by event handlers.
|
||||
|
||||
We want that "on-click" as simple as we can make it.
|
||||
|
||||
**Rule**: `views` are as passive and minimal as possible when it
|
||||
comes to handling events. They `dispatch` pure data and nothing more.
|
||||
|
||||
## Prefer Dumb Views - Part 2
|
||||
|
||||
Neither do we want views computing the data they render.
|
||||
That's the job of a subscription:
|
||||
|
||||
So this is bad:
|
||||
```clj
|
||||
(defn show-items
|
||||
[]
|
||||
(let [sorted-items (sort @(subscribe [:items]))] ;; <--
|
||||
(into [:div] (for [i sorted-items] [item-view i]))))
|
||||
```
|
||||
|
||||
The view is not simply taking the data supplied by the
|
||||
|
||||
|
||||
## Full Stack
|
||||
|
||||
If you like re-frame and want to take the principles full-stack, then
|
||||
these resource might be interesting:
|
||||
|
||||
Commander Pattern
|
||||
Commander Pattern
|
||||
https://www.youtube.com/watch?v=B1-gS0oEtYc
|
||||
|
||||
Datalog All The Way Down
|
||||
https://www.youtube.com/watch?v=aI0zVzzoK_E
|
||||
|
@ -58,7 +58,7 @@ Architecturally, re-frame implements "a perpetual loop".
|
||||
To build an app, you hang pure functions on certain parts of this loop,
|
||||
and re-frame looks after the `conveyance of data`
|
||||
around the loop, into and out of the transforming functions you
|
||||
provide - hence the tag line "Derived Data, Flowing".
|
||||
provide - which is why the tag line is "Derived Data, Flowing".
|
||||
|
||||
### It does Physics
|
||||
|
||||
@ -70,7 +70,7 @@ Two distinct stages, involving water in different phases, being acted upon
|
||||
by different forces: gravity working one way, evaporation/convection the other.
|
||||
|
||||
To understand re-frame, **imagine data flowing around that loop instead of water**. re-frame
|
||||
provides the "conveyance" of the data - the equivalent of gravity, evaporation and convection.
|
||||
provides the conveyance of the data - the equivalent of gravity, evaporation and convection.
|
||||
You design what's flowing and then you hang functions off the loop at
|
||||
various points to compute the data's phase changes.
|
||||
|
||||
@ -86,12 +86,14 @@ you to understand re-frame, is **practically proof** it does physics.
|
||||
<img align="right" src="/images/Readme/Dominoes-small.jpg?raw=true">
|
||||
|
||||
Computationally, each iteration of the loop involves a
|
||||
6 domino cascade.
|
||||
6 domino cascade.
|
||||
|
||||
One domino triggers the next, which triggers the next, etc,
|
||||
until we are back at the beginning of the loop. Each iteration is the same cascade.
|
||||
|
||||
### 1st Domino
|
||||
Here are the 6 dominoes ...
|
||||
|
||||
### 1st Domino
|
||||
|
||||
An `event` is sent when something happens - the user
|
||||
clicks a button, or a websocket receives a new message.
|
||||
@ -107,11 +109,11 @@ re-frame is `event` driven.
|
||||
In response to an `event`, an application must compute
|
||||
the implication (the ambition, the intent). This is known as `event handling`.
|
||||
|
||||
Event handler functions compute `effects`. Or, more accurately, they compute
|
||||
a **description of `effects`**, which means they say, declaratively,
|
||||
how the world should change (because of the event).
|
||||
Event handler functions compute `effects` or, more accurately,
|
||||
a **description of `effects`**. So they compute a data structure
|
||||
which says, declaratively, how the world should change (because of the event).
|
||||
|
||||
Much of the time, only the state of the SPA itself need
|
||||
Much of the time, only the "app state" of the SPA itself need
|
||||
change, but sometimes the outside world must too must be effected
|
||||
(localstore, cookies, databases, emails, logs, etc).
|
||||
|
||||
@ -131,26 +133,27 @@ it does so in a controlled, debuggable, auditable, mockable, plugable way.
|
||||
|
||||
### Then what happens?
|
||||
|
||||
So, that 3rd domino just changed the world and, very often, that involves
|
||||
So, that 3rd domino just changed the world and, very often,
|
||||
one particular part of the world, namely the **app's state**.
|
||||
|
||||
re-frame `app state` is held in one place - think of it like you
|
||||
re-frame's `app state` is held in one place - think of it like you
|
||||
would an in-memory, central database for the app.
|
||||
|
||||
When domino 3 changes `app state`, it triggers the next part of the cascade
|
||||
When domino 3 changes this `app state`, it triggers the next part of the cascade
|
||||
involving dominoes 4-5-6.
|
||||
|
||||
### The view formula
|
||||
|
||||
The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library:
|
||||
`v = f(s)`
|
||||
`v = f(s)`
|
||||
|
||||
A view `v` is a function `f` of the app state `s`.
|
||||
|
||||
Or, said another way, there are functions `f` which compute what DOM nodes, `v`,
|
||||
should be displayed to the user when the application is in a given app state, `s`.
|
||||
|
||||
Or, another way: **over time**, as `s` changes, `f`
|
||||
will be called each time to compute new `v`, forever keeping `v` up to date with the current `s`.
|
||||
will be re-run each time to compute new `v`, forever keeping `v` up to date with the current `s`.
|
||||
|
||||
Now, in our case, it is domino 3 which changes `s`, the application state,
|
||||
and, in response, dominoes 4-5-6 are about re-running `f` to compute the new `v`
|
||||
@ -164,6 +167,9 @@ even be showing right now.
|
||||
|
||||
### Domino 4
|
||||
|
||||
Domino 4 is about extracting data from "app state". The right data,
|
||||
in the right format for view functions (Domino 5).
|
||||
|
||||
Domino 4 is a novel and efficient de-duplicated signal graph which
|
||||
runs query functions on the app state, `s`, efficiently computing
|
||||
reactive, multi-layered, "materialised views" of `s`.
|
||||
@ -196,7 +202,7 @@ browser DOM nodes are mutated.
|
||||
can be be described, understood and
|
||||
tested independently. They take data, transform it and return new data.
|
||||
|
||||
The loop itself is utterly predictable and very mechanical in operation.
|
||||
The loop itself is very mechanical in operation.
|
||||
So, there's a regularity, simplicity and
|
||||
certainty to how a re-frame app goes about its business,
|
||||
which leads, in turn, to an ease in reasoning and debugging.
|
||||
@ -205,22 +211,21 @@ which leads, in turn, to an ease in reasoning and debugging.
|
||||
|
||||
The two sub-cascades 1-2-3 and 4-5-6 have a similar structure.
|
||||
|
||||
In each cascade, it is the 2nd to last domino which
|
||||
computes "descriptions" of mutations required and it is
|
||||
the last domino which actions these descriptions - it does the dirty work.
|
||||
In each, it is the 2nd last domino which
|
||||
computes "descriptions" of mutations required, and it is
|
||||
the last domino which does rthe dirty work and realises these descriptions.
|
||||
|
||||
And in both case, you need worry yourself about this dirty work. re-frame looks
|
||||
And in both case, you don't need to worry yourself about this dirty work. re-frame looks
|
||||
after those dominoes.
|
||||
|
||||
## Code Fragments
|
||||
|
||||
Time to understand this
|
||||
domino narrative in terms of code fragments.
|
||||
Let's now understand this
|
||||
domino narrative in terms of code fragments.
|
||||
|
||||
> You shouldn't expect
|
||||
to fully grok all the code presented below. We're still in overview mode, getting
|
||||
the 30,000 foot view, and
|
||||
detail is missing. Plenty of tutorials and examples to follow.
|
||||
to completely grok the code presented below. We're still in overview mode, getting
|
||||
the 30,000 foot view. There are later tutorials for the details.
|
||||
|
||||
**Imagine:** the UI of an SPA shows a list of items. This user
|
||||
clicks the "delete" button next to the 3rd item in a list.
|
||||
@ -238,8 +243,8 @@ like this:
|
||||
|
||||
`dispatch` is the means by which you emit an `event`. An `event` is a vector and, in this case,
|
||||
it has 2 elements: `[:delete-item 2486]`. The first element,
|
||||
`:delete-item`, is the kind of event. The `rest` is optional, and is whatever else needs to
|
||||
be known about the event - in this case, my made-up id, `2486`, for the item to delete.
|
||||
`:delete-item`, is the kind of event. The `rest` is optional, further data about the `event`
|
||||
- in this case, my made-up id, `2486`, for the item to delete.
|
||||
|
||||
### Code For Domino 2
|
||||
|
||||
@ -249,7 +254,7 @@ The `event handler`, `h`, associated with
|
||||
This handler function, `h`, must take two arguments: the state-of-the-world
|
||||
and the event, and it must return an effects map. Without going into any
|
||||
explanations at this early point, here's a sketch of what a handler
|
||||
might look like:
|
||||
might look like:
|
||||
```clj
|
||||
(defn h
|
||||
[{:keys [db]} event] ;; args: db from coeffect, event
|
||||
@ -257,30 +262,36 @@ might look like:
|
||||
{:db (dissoc-in db [:items item-id])})) ;; effect is change db
|
||||
```
|
||||
|
||||
Sometime earlier, this event handler (function) `h` would have been associated with `:delete-item` in this way:
|
||||
On program starup, this event handler (function) `h` would have been
|
||||
associated with `:delete-item` in this way:
|
||||
```clj
|
||||
(re-frame.core/reg-event-fx :delete-item h)
|
||||
```
|
||||
|
||||
### Code For Domino 3
|
||||
|
||||
An `effect handler` (function) actions the `effect` returned by the call to `h`.
|
||||
That effect was the map:
|
||||
An `effect handler` (function) actions the `effect` returned by the call to `h`.
|
||||
That `effect` was the map:
|
||||
```clj
|
||||
{:db (dissoc-in db [:items item-id])}
|
||||
```
|
||||
Keys in this map identify the required effect, with the values supplying further details.
|
||||
Keys in this map identify the required `effect`, with the values of the map
|
||||
supplying further details.
|
||||
|
||||
A key of `:db` means to update the app state, with the value supplied.
|
||||
A key of `:db` means to update the app state, with the new computed value.
|
||||
|
||||
This is a mutative
|
||||
step, facilitated by re-frame, which you won't have to do explicitly.
|
||||
The update of "app state", which re-frame manages for you,
|
||||
is a mutative step, facilitated by re-frame itself
|
||||
when it sees the `:db` effect.
|
||||
|
||||
Why the name `:db`? re-frame sees "app state" as something of an in-memory
|
||||
database.
|
||||
|
||||
### Code For Domino 4
|
||||
|
||||
Because the app state changed, a query (function) over this app
|
||||
state is called automatically (reactively), and it computes the list of items (which,
|
||||
because of domino 3, has been updated to no longer contain the deleted item).
|
||||
Because a new version of "app state" has been computed and installed,
|
||||
a query (function) over this app state is called automatically (reactively),
|
||||
itself computing the list of items.
|
||||
|
||||
Because the items
|
||||
are stored in app state, there's not a lot to compute in this case. This
|
||||
@ -291,14 +302,15 @@ subscription acts more like an accessor.
|
||||
(:items db)) ;; not much of materialised view
|
||||
```
|
||||
|
||||
Such a query-fn must be registered, (reasons become obvious in the next domino) like this:
|
||||
On program startup, such a query-fn must be registered,
|
||||
(for reasons obvious in the next domino) like this:
|
||||
```clj
|
||||
(re-frame.core/reg-sub :query-items query-fn)
|
||||
```
|
||||
|
||||
### Code For Domino 5
|
||||
|
||||
Because the query function computed a new value, a view (function) which subscribes
|
||||
Because the query function re-computed a new value, a view (function) which subscribes
|
||||
to that value, is called automatically (reactively) to re-compute DOM. It produces
|
||||
a hiccup-formatted data structure describing the DOM nodes required (no DOM nodes
|
||||
for the deleted item, obviously, but otherwise the same DOM as last time).
|
||||
@ -310,7 +322,7 @@ for the deleted item, obviously, but otherwise the same DOM as last time).
|
||||
[div: (map item-render @items])) ;; assume item-render already written
|
||||
```
|
||||
|
||||
Notice how `items` is "sourced". A view function uses `subscribe` with a key
|
||||
Notice how `items` is "sourced" from "app state". View function use `subscribe` with a key
|
||||
originally used to register a query function.
|
||||
|
||||
### Code For Domino 6
|
||||
@ -321,16 +333,34 @@ The DOM "this
|
||||
time" is the same as last time, except for the absence of DOM for the
|
||||
deleted item.
|
||||
|
||||
The key point to understand about 3-4-5-6 is that a change to app state, triggers queries to rerun,
|
||||
which, in turn, triggers views to rerun which, in turn, causes fresh DOM in the broiwser All reactively.
|
||||
Boom, boom, boom.
|
||||
One domino after the other. But with efficiency short circuits.
|
||||
### 3-4-5-6 Summary
|
||||
|
||||
The key point to understand about our 3-4-5-6 example is:
|
||||
- a change to app state ...
|
||||
- triggers query functions to rerun ...
|
||||
- which triggers view functions to rerun
|
||||
- which causes new DOM
|
||||
|
||||
Boom, boom, boom go the dominoes.
|
||||
|
||||
It is a reactive data flow.
|
||||
|
||||
### Aaaaand we're done
|
||||
|
||||
At this point, the re-frame app returns to a quiescent state,
|
||||
waiting for the next event.
|
||||
|
||||
## Your Job
|
||||
|
||||
When building a re-frame app, you will:
|
||||
- design your app's information model (data and schema layer)
|
||||
- write and register event handler functions (control and transition layer) (domino 2)
|
||||
- (once in a blue moon) write and register effect and coeffect handler
|
||||
functions (domino 3) which do the mutative dirty work of which we dare not
|
||||
speak.
|
||||
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
|
||||
- write Reagent view functions (view layer) (domino 5)
|
||||
|
||||
## It Leverages Data
|
||||
|
||||
You might already know that ClojureScript is a modern lisp, and that
|
||||
@ -412,8 +442,7 @@ Client only: https://github.com/Day8/re-frame-template <br>
|
||||
Front and back: http://www.luminusweb.net/
|
||||
|
||||
Use these resources: <br>
|
||||
https://github.com/Day8/re-com
|
||||
XXX
|
||||
https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
|
||||
|
||||
###
|
||||
|
@ -1,4 +1,10 @@
|
||||
### [re-frame Introduction](../README.md)
|
||||
### Introduction
|
||||
This section contains a WIP rewrite of the existing README
|
||||
into a number of smaller documents, with a different way of explaining re-frame.
|
||||
- [re-frame's New README (soon)](NewREADME.md)
|
||||
- [App State](ApplicationState.md)
|
||||
- [Code Walk-Through](CodeWalkThrough.md)
|
||||
- [Mental Model Omnibus](MentalModelOmnibus.md)
|
||||
|
||||
|
||||
### Understanding Event Handlers
|
||||
|
@ -564,3 +564,48 @@ Back to the more pragmatic world ...
|
||||
[Prismatic Schema]:https://github.com/Prismatic/schema
|
||||
[Hoplon]:http://hoplon.io/
|
||||
[Pedestal App]:https://github.com/pedestal/pedestal-app
|
||||
|
||||
|
||||
-----------------
|
||||
|
||||
## Prefer Dumb Views - Part 1
|
||||
|
||||
Many events are dispatched by the DOM in response to user actions.
|
||||
|
||||
For example, a button view might be like this:
|
||||
```clj
|
||||
(defn yes-button
|
||||
[]
|
||||
[:div {:class "button-class"
|
||||
:on-click #(dispatch [:yes-button-clicked])}
|
||||
"Yes"])
|
||||
```
|
||||
|
||||
Notice that `on-click` DOM handler:
|
||||
```clj
|
||||
#(dispatch [:yes-button-clicked])
|
||||
```
|
||||
|
||||
With re-frame, we want the DOM as passive as possible. We do
|
||||
not want our views containing any imperative control logic.
|
||||
All of that should be computed by event handlers.
|
||||
|
||||
We want that "on-click" as simple as we can make it.
|
||||
|
||||
**Rule**: `views` are as passive and minimal as possible when it
|
||||
comes to handling events. They `dispatch` pure data and nothing more.
|
||||
|
||||
## Prefer Dumb Views - Part 2
|
||||
|
||||
Neither do we want views computing the data they render.
|
||||
That's the job of a subscription:
|
||||
|
||||
So this is bad:
|
||||
```clj
|
||||
(defn show-items
|
||||
[]
|
||||
(let [sorted-items (sort @(subscribe [:items]))] ;; <--
|
||||
(into [:div] (for [i sorted-items] [item-view i]))))
|
||||
```
|
||||
|
||||
The view is not simply taking the data supplied by the
|
||||
|
Loading…
x
Reference in New Issue
Block a user