This commit is contained in:
Mike Thompson 2016-12-04 07:57:31 +11:00
parent 31ce3903bf
commit 765b116ae6
7 changed files with 181 additions and 176 deletions

View File

@ -2,17 +2,13 @@
Let's understand how re-frame manages application state. 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) - [On Data](#on-data)
- [The Big Ratom](#the-big-ratom) - [The Big Ratom](#the-big-ratom)
- [The Benefits Of Data-In-The-One-Place](#the-benefits-of-data-in-the-one-place) - [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 ### 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>&mdash; Fogus (@fogus) <a href="https://twitter.com/fogus/status/454582953067438080">April 11, 2014</a></blockquote> <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>&mdash; 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 to hide state. I still wake up in a sweat some nights thinking about all
that Clojure data lying around exposed and passive. 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: In re-frame's reference implementation, `app-db` is one of these:
```clj ```clj
@ -62,7 +58,7 @@ Further Notes:
be a [datascript](https://github.com/tonsky/datascript database). In fact, any database which 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 - 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 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 ### The Benefits Of Data-In-The-One-Place

View File

@ -1,26 +1,3 @@
<!-- 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 ## Initial Code Walk-through
At this point in your reading, you are armed with: At this point in your reading, you are armed with:
@ -32,8 +9,7 @@ In this tutorial, **we'll look at re-frame code**. Finally.
### What Code? ### What Code?
This repo contains an `/example` application called "simple", This repo contains an `/example` application called "simple",
which has around 70 lines of code. We'll look at every line which has around 70 lines of code. We'll look at every line.
and understand what it does.
You are currently about 50% the way to understanding re-frame. By the 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 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. here, to minimise cognitive load, we'll cut that corner.
But ... we can't cut it completely. You'll still need an 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: a two-key map like this:
```cljs ```cljs
{:time (js/Date.) ;; current time for display {:time (js/Date.) ;; current time for display
:time-color "#f88"} ;; what colour should the time be shown in :time-color "#f88"} ;; the colour in which the time should be be shown
``` ```
re-frame itself owns/manages `app-db` (see FAQ #1), re-frame itself owns/manages `app-db` (see FAQ #1), and it will
supplying the value within it (a two-key map in this case) supply the value within it (a two-key map in this case)
to your various handlers as required. to your various handlers as required.
## Events (domino 1) ## Events (domino 1)
@ -145,7 +121,7 @@ The `router`:
for this kind of event for this kind of event
3. calls that event handler with the necessary arguments 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. for each kind of event.
## Event Handlers (domino 2) ## Event Handlers (domino 2)
@ -173,7 +149,7 @@ The handler function you provide should expect two parameters:
- `db` the current application state - `db` the current application state
- `v` the event vector - `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 Each event handler must compute and return the new state of
the application, which means it normally returns a the application, which means it normally returns a
@ -192,14 +168,15 @@ to be computed. More on this soon.
### :initialize ### :initialize
On startup, application state must be initialised. We 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 So a `(dispatch [:initialize])` will happen early in the
apps life (more on this below), and we need to write an `event handler` apps life (more on this below), and we need to write an `event handler`
for it. for it.
Now this event handler is slightly unusual because it doesn't 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. in a new complete value.
Like this: Like this:
@ -216,7 +193,7 @@ This particular handler `fn` ignores the two parameters
a map literal, which becomes the application a map literal, which becomes the application
state. 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 ```clj
(rf/reg-event-db (rf/reg-event-db
:initialize :initialize
@ -226,8 +203,6 @@ Here's an alternative way of writing it:
(assoc :time-color "#f88"))) (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 ### :timer
@ -286,27 +261,25 @@ source data from other subscriptions. So a tree of dependencies
results. results.
The Views (Domino 5) are the leaves. The root is `app-db` and the The Views (Domino 5) are the leaves. The root is `app-db` and the
intermediate nodes are computations being performed by intermediate nodes are computations being performed by Domino 4 query functions.
Each query is identified by an `id` XXXX
Now, the two examples below are utterly trivial. They just extract part of the application 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. subscriptions and more explanation can be found in the todomvc example.
`reg-sub` associates a `query id` with a function which computes `reg-sub` associates a `query id` with a function which computes
that query. It's use looks like this: that query. It's use looks like this:
```clj ```clj
(reg-sub (reg-sub
:some-query-id ;; query identifier :some-query-id ;; query id (used later in subscribe)
some-function) ;; the function which will compute the query a-query-fn) ;; the function which will compute the query
``` ```
If, later, we see a view function requesting data like this: If, later, we see a view function requesting data like this:
`(listen [:some-query-id])` ;; note use of `:some-query-id` XXX using listen `(subscribe [:some-query-id])` ;; note use of `:some-query-id`
then `some-function` will be used to perform the query over application state. then `a-query-fn` will be used to perform the query over application state.
Each time application state changes, `some-function` will be Each time application state changes, `a-query-fn` will be
called again to compute a new materialised view (a new computation) 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 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 `: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). to compute new DOM (because it depends on a query value which changed).
@ -328,7 +301,7 @@ Here's the code:
(:time-color db))) (: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) ## 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. 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 In order to render a DOM representation of the application state, view functions
must first obtain that state. This happens via subscriptions. 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 transform data into data. They source
data from subscriptions (queries across application state), and data from subscriptions (queries across application state), and
the data they return is hiccup-formatted, which is a proxy for DOM. the data they return is hiccup-formatted, which is a proxy for DOM.
@ -460,4 +437,3 @@ the synchronous
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4) - write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
- write Reagent view functions (view layer) (domino 5) - write Reagent view functions (view layer) (domino 5)
re-frame's job is to XXX

View File

@ -32,7 +32,7 @@ and get you reading and writing code ASAP.
**But** there are other interesting perspectives on re-frame **But** there are other interesting perspectives on re-frame
which will deepen your understanding of its design, 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 This tutorial is a tour
of these ideas, justifications and insights. It is a little rambling, but I 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. delivers only the 'V' part of a traditional MVC framework.
But apps involve much more than V. We build quite complicated 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], We read up on [Pedestal App], [Flux],
[Hoplon], [Om], early [Elm], etc and re-frame is the architecture that [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. a system out of pure functions.
__Third__, we believe in the primacy of data, for the reasons described in __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 the main README. re-frame has a data oriented, functional architecture.
implements an infinite loop of Derived data.
__Fourth__, we believe that Reactive Programming is one honking good idea. __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 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. step by step towards the error causing problem.
This is perfect for debugging assuming, of course, you are in a position to capture 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). 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`. 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 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 and interesting mental model. We were first exposed to this idea
of `foldp` (fold from the past), which was later enshrined in the via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
Elm Architecture. Elm Architecture.
And for the love of all that is good, please watch this terrific 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), Think about that: shared mutable state (the root of all evil),
re-imagined as a stream!! Blew my socks off. re-imagined as a stream!! Blew my socks off.
If, by chance, you have watched that video, you might twig to 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 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`. 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, 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 ## It does FSM
> Any sufficiently complicated GUI contains an ad hoc, > Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation > informally-specified, bug-ridden, slow implementation
> of a hierarchical Finite State Machine <br> > 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 `event handlers` collectively
implement the "control" part of an application. Their logic 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? My job is to be a relentless cheerleader for re-frame, right?
The gyrations of my Pom-Poms should be tectonic, 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. be taught in all ComSci courses.
> We begin in admiration and end by organizing our disappointment <br> > We begin in admiration and end by organizing our disappointment <br>
> &nbsp;&nbsp;&nbsp; -- Gaston Bachelard (French philosopher) > &nbsp;&nbsp;&nbsp; -- 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!! But, no. No! Those French Philosophers and their pessimism - ignore him!!
Your love for re-frame will be deep, abiding and enriching. Your love for re-frame will be deep, abiding and enriching.
@ -259,63 +260,15 @@ Using data gives us:
- logability and event sourcing - logability and event sourcing
- a more flexible version of "partial" (curring) - 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 ## 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 https://www.youtube.com/watch?v=B1-gS0oEtYc
Datalog All The Way Down
https://www.youtube.com/watch?v=aI0zVzzoK_E

View File

@ -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, To build an app, you hang pure functions on certain parts of this loop,
and re-frame looks after the `conveyance of data` and re-frame looks after the `conveyance of data`
around the loop, into and out of the transforming functions you 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 ### 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. 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 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 You design what's flowing and then you hang functions off the loop at
various points to compute the data's phase changes. various points to compute the data's phase changes.
@ -91,6 +91,8 @@ Computationally, each iteration of the loop involves a
One domino triggers the next, which triggers the next, etc, 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. until we are back at the beginning of the loop. Each iteration is the same cascade.
Here are the 6 dominoes ...
### 1st Domino ### 1st Domino
An `event` is sent when something happens - the user An `event` is sent when something happens - the user
@ -107,11 +109,11 @@ re-frame is `event` driven.
In response to an `event`, an application must compute In response to an `event`, an application must compute
the implication (the ambition, the intent). This is known as `event handling`. the implication (the ambition, the intent). This is known as `event handling`.
Event handler functions compute `effects`. Or, more accurately, they compute Event handler functions compute `effects` or, more accurately,
a **description of `effects`**, which means they say, declaratively, a **description of `effects`**. So they compute a data structure
how the world should change (because of the event). 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 change, but sometimes the outside world must too must be effected
(localstore, cookies, databases, emails, logs, etc). (localstore, cookies, databases, emails, logs, etc).
@ -131,26 +133,27 @@ it does so in a controlled, debuggable, auditable, mockable, plugable way.
### Then what happens? ### 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**. 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. 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. involving dominoes 4-5-6.
### The view formula ### The view formula
The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library: 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`. 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`, 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`. 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` 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, 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` 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
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 Domino 4 is a novel and efficient de-duplicated signal graph which
runs query functions on the app state, `s`, efficiently computing runs query functions on the app state, `s`, efficiently computing
reactive, multi-layered, "materialised views" of `s`. reactive, multi-layered, "materialised views" of `s`.
@ -196,7 +202,7 @@ browser DOM nodes are mutated.
can be be described, understood and can be be described, understood and
tested independently. They take data, transform it and return new data. 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 So, there's a regularity, simplicity and
certainty to how a re-frame app goes about its business, certainty to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging. 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. 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 In each, it is the 2nd last domino which
computes "descriptions" of mutations required and it is computes "descriptions" of mutations required, and it is
the last domino which actions these descriptions - it does the dirty work. 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. after those dominoes.
## Code Fragments ## Code Fragments
Time to understand this Let's now understand this
domino narrative in terms of code fragments. domino narrative in terms of code fragments.
> You shouldn't expect > You shouldn't expect
to fully grok all the code presented below. We're still in overview mode, getting to completely grok the code presented below. We're still in overview mode, getting
the 30,000 foot view, and the 30,000 foot view. There are later tutorials for the details.
detail is missing. Plenty of tutorials and examples to follow.
**Imagine:** the UI of an SPA shows a list of items. This user **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. 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, `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, 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 `:delete-item`, is the kind of event. The `rest` is optional, further data about the `event`
be known about the event - in this case, my made-up id, `2486`, for the item to delete. - in this case, my made-up id, `2486`, for the item to delete.
### Code For Domino 2 ### Code For Domino 2
@ -257,7 +262,8 @@ might look like:
{:db (dissoc-in db [:items item-id])})) ;; effect is change db {: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 ```clj
(re-frame.core/reg-event-fx :delete-item h) (re-frame.core/reg-event-fx :delete-item h)
``` ```
@ -265,22 +271,27 @@ Sometime earlier, this event handler (function) `h` would have been associated w
### Code For Domino 3 ### Code For Domino 3
An `effect handler` (function) actions the `effect` returned by the call to `h`. An `effect handler` (function) actions the `effect` returned by the call to `h`.
That effect was the map: That `effect` was the map:
```clj ```clj
{:db (dissoc-in db [:items item-id])} {: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 The update of "app state", which re-frame manages for you,
step, facilitated by re-frame, which you won't have to do explicitly. 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 ### Code For Domino 4
Because the app state changed, a query (function) over this app Because a new version of "app state" has been computed and installed,
state is called automatically (reactively), and it computes the list of items (which, a query (function) over this app state is called automatically (reactively),
because of domino 3, has been updated to no longer contain the deleted item). itself computing the list of items.
Because the items Because the items
are stored in app state, there's not a lot to compute in this case. This 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 (: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 ```clj
(re-frame.core/reg-sub :query-items query-fn) (re-frame.core/reg-sub :query-items query-fn)
``` ```
### Code For Domino 5 ### 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 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 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). 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 [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. originally used to register a query function.
### Code For Domino 6 ### 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 time" is the same as last time, except for the absence of DOM for the
deleted item. deleted item.
The key point to understand about 3-4-5-6 is that a change to app state, triggers queries to rerun, ### 3-4-5-6 Summary
which, in turn, triggers views to rerun which, in turn, causes fresh DOM in the broiwser All reactively.
Boom, boom, boom. The key point to understand about our 3-4-5-6 example is:
One domino after the other. But with efficiency short circuits. - 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 ### Aaaaand we're done
At this point, the re-frame app returns to a quiescent state, At this point, the re-frame app returns to a quiescent state,
waiting for the next event. 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 ## It Leverages Data
You might already know that ClojureScript is a modern lisp, and that 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/ Front and back: http://www.luminusweb.net/
Use these resources: <br> Use these resources: <br>
https://github.com/Day8/re-com https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
XXX
### ###

View File

@ -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 ### Understanding Event Handlers

View File

@ -564,3 +564,48 @@ Back to the more pragmatic world ...
[Prismatic Schema]:https://github.com/Prismatic/schema [Prismatic Schema]:https://github.com/Prismatic/schema
[Hoplon]:http://hoplon.io/ [Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app [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