mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-24 07:48:14 +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.
|
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>— 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>— 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
|
@ -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
|
|
@ -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>
|
||||||
> -- Gaston Bachelard (French philosopher)
|
> -- 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
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user