WIP on the new README and initial docs

This commit is contained in:
Mike Thompson 2016-10-21 07:07:38 +11:00
parent fa39d73223
commit 0638becd45
9 changed files with 701 additions and 553 deletions

View File

@ -0,0 +1,294 @@
## An Initial Code Walk Through
Now you are armed with a high level, conceptual understanding
of the 5 domino process, and an understanding of application state,
we'll look at some code.
All 80 lines of the "simple" example application is given below,
in domino order, heavily annotated with explanation.
### What this App Does
This app displays the time at second resolution. It also allows
has one input field, into which you can input the colour to
use in the display of the time.
XXX screenshot
XXX How to run it
### Namespace
Because this example is so simple, all the code is in a single namespace.
Within it, we'll need access to both `reagent` and `re-frame`. So, we start like this:
```clj
(ns simple.core
(:require [reagent.core :as reagent]
[re-frame.core :as rf]))
```
### Data Schema
I recommended you always write a good quality schema for your application
state. But, here, I'm going to break my rule to keep things simple and stay
focused on the domino code.
Suffice it to say the application state for "simple" looks like this
map with two keys:
```cljs
{:time (js/Date.)
:time-color "#f88"}
```
Remember that re-frame holds/manages application state for you,
supplying it to your various handlers when it is needed.
## Events (domino 1)
Events are data. You choose the format.
In our re-frame reference implementation we choose a vector
format for events. For example:
```clj
[:delete-item 42]
```
The first element in the vector identifies the kind of event. The
further elements are optional, additional data associated with the event
-- the additional value above (42) is presumably the id
of the item to delete.
Here are some other example events:
```clj
[:yes-button-clicked]
[:set-spam-wanted false :user-override "hello"]
[:some-ns/on-success response]
```
**Rule**: events are pure data. No dirty tricks like putting callback functions on the wire.
You know who you are.
### dispatch
To send an event you call `dispatch` with the event as the argument:
```clj
(dispatch [:event-id value1 value2])
```
In this "simple" app, a `:timer` event is sent every second:
```clj
(defn dispatch-timer-event
[]
(let [now (js/Date.)]
(rf/dispatch [:timer now])))
;; call the dispatching function every second
(defonce time-updater (js/setInterval dispatch-timer-event 1000))
```
This is a little unusual. Normally, it is an app's UI widgets which
`dispatch` events (in response to user actions), or an HTTP POST's
`on-success` handler, or a web socket which gets a new packet.
### After dispatch
When `dispatch` is passed an event vector, it just puts that
event onto a conveyor belt for processing.
The event is not processes synchronously. It happens **later**.
The consumer on the end of the conveyor is a `router` which will organise for that
event to be processed by the right handler.
## Event Handlers (domino 2)
Collectively, event handlers provide the control logic in a re-frame application.
We must next register functions to "handle events".
This application has three events, identified by keywords:
:initialise
:time-color-change
:timer
Because there's 3 events, below you'll see 3 calls to `reg-event-db`,
each registering a handler for an event, and each occurrence like this:
```clj
(reg-event-db
:id-for-the-event
function-to-handle-the-event)
```
An `event handler` registered via `reg-event-db` looks like this
`(fn [db v] ...)`.
It takes two parameters, the current application state `db`
and the event vector `v` originally dispatched.
re-frame will ensure the right
An event handler must compute and return the new state of
the application, which means it normally returns a
modified version of `db`.
Below, you'll sometimes see the event handlers written:
```clj
(fn [db [_ something]] <-- 2nd vec param destructured to get payload value
...)
``
Note: technically, an event handler return `effects`. We're using
`reg-event-db` here which assumes the only `effect` required (for this event)
is a change to application state. When you need to do other effects,
like sending emails, or http POSTing, or writing to localstore, you
would use the more sophisticated `reg-event-fx` (note the trailing -fx) which
is described in more advanced tutorials. We're keeping it simple for the moment.
```clj
(rf/reg-event-db ;; sets up initial application state
:initialize ;; usage: (dispatch [:initialize])
(fn [_ _] ;; the two parameters are not important here, so use _
{:time (js/Date.) ;; What it returns becomes the new application state
:time-color "#f88"})) ;; so the application state will initially be a map with two keys
(rf/reg-event-db ;; usage: (dispatch [:time-color-change 34562])
:time-color-change ;; dispatched when the user enters a new colour into the UI
(fn [db [_ new-color-value]] ;; -db event handlers given 2 parameters: current application state and event (a vector)
(assoc db :time-color new-color-value))) ;; compute and return the new application state
(rf/reg-event-db ;; usage: (dispatch [:timer a-js-Date])
:timer ;; every second an event of this kind will be dispatched
(fn [db [_ new-time]] ;; note how the 2nd parameter is desctructure to obtain the data value
(assoc db :time new-time))) ;; compute and return the new application state
```
## Effect Handlers (domino 3)
Event handlers produce `effects` which have to be actioned.
In this "simple" application, we're using the simplest kind of
event handlers. They produce only one effect "please update application state".
This is handled for us automatically by re-frame. Nothing for us to do.
This is not unusual. You'll almost never have to write effect handlers, but
we'll understand more about them in a later tutorial.
## Subscription Handlers (domino 4)
These handlers are given application state as a parameter, and they
perform a query (computation) over it, returning a "materialised view"
of that state.
These queries are used in the view functions which need to render
DOM.
Now, the examples below are utterly trivial. They just extract part of the application
state and return it. So, virtually no computation. More interesting
subscriptions and more explanation can be found in the todomvc example.
`reg-sub` associates a `query identifier` 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
```
If ever a view requests data like this:
`(listen [:some-query-id])` ;; note use of `:some-query-id`
then `some-function` will be used to perform the query over applciation state
when a
Each time application state changes, `some-function` will be
called again to compute a new materialised view (a new computation)
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 create new DOM (because it depends on a query value which changed).
re-frame will ensure the necessary calls are made, at the right time.
```clj
(rf/reg-sub
:time
(fn [db _] ;; db is current app state. 2nd usused param is query vector
(:time db))) ;; return a query computation over the application state
(rf/reg-sub
:time-color
(fn [db _]
(:time-color db)))
```
## View Functions (domino 5)
This is where we render the application's UI using Reagent/React.
As functions do, these `views` 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.
Data -> HTML
They source data from:
1. arguments (aka props in the React world)
2. queries which obtain data from the application state
Because of the 2nd source, these functions are not pure. XXX
Notice that color-input below does a `dispatch`. It is very common for UI widgets
to be event-dispatching. The user interacting with the GUI is a major source of
events.
```clj
(defn clock
[]
[:div.example-clock
{:style {:color (rf/listen [:time-color])}}
(-> (rf/listen [:time])
.toTimeString
(clojure.string/split " ")
first)])
(defn color-input
[]
[:div.color-input
"Time color: "
[:input {:type "text"
:value (rf/listen [:time-color])
:on-change #(rf/dispatch [:time-color-change (-> % .-target .-value)])}]]) ;; <---
(defn ui
[]
[:div
[:h1 "Hello world, it is now"]
[clock]
[color-input]])
```
## Kick Starting The App
Below, `run` is the function called when the HTML loads. It kicks off the
application.
It has two tasks:
1. load the initial application state
2. "mount" the GUI on an existing DOM element. Causes an initial render.
```clj
(defn ^:export run
[]
(dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into '<div id="app" />'
(js/document.getElementById "app")))
```
After `run` is called, the app passively waits for events.
Nothing happens without an `event`.
When it comes to establishing initial application state, you'll
notice the use of `dispatch-sync`, rather than `dispatch`. This is
the synchronous

View File

@ -0,0 +1,99 @@
## Application State
In our first high level code walk through, there was talk of
"application state". It was supplied to event handlers and there was talk about
query functions computing materialised views from it, etc.
Let's now look at it in more depth.
### 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>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
### The Big Ratom
re-frame puts all your application state into one place, which is
called `app-db`.
Ideally, you will provide a spec for this data in the one place,
[using a powerful and leveragable schema](http://clojure.org/about/spec).
Now, this advice is not the slightest bit controversial for 'real' databases, right?
You'd happily put all your well-formed data into PostgreSQL.
But within a running application (in memory), there is hesitation. If you have
a background in OO, this data-in-one-place
business is a really, really hard one to swallow. You've
spent your life breaking systems into pieces, organised around behaviour and trying
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 perfect.
In re-frame's reference implementation, `app-db` is one of these:
```clj
(def app-db (reagent/atom {})) ;; a Reagent atom, containing a map
```
Although it is a `Reagent atom` (hereafter `ratom`), I'd encourage
you to think of it as an in-memory database. It will contain structured data.
You will need to query that data. You will perform CRUD
and other transformations on it. You'll often want to transact on this
database atomically, etc. So "in-memory database"
seems a more useful paradigm than plain old map-in-atom.
Further Notes:
1. `app-state` would probably be a more accurate name, but I choose `app-db` instead because
I wanted to convey the database notion as strongly as possible.
2. In the documentation and code, I make a distinction between `app-db` (the `ratom`) and
`db` which is the (map) `value` currently stored **inside** this `ratom`.
3. the reference implementation creates and manages an `app-db` for you, so
you don't need to declare one yourself (see the 1st FAQ if you want to inspect the value it holds).
4. `app-db` doesn't actually have to be a `ratom` containing a map. It could, for example,
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).
### The Benefits Of Data-In-The-One-Place
1. Here's the big one: because there is a single source of truth, we write no
code to synchronize state between many different stateful components. I
cannot stress too much how significant this is. You end up writing less code
and an entire class of bugs is eliminated.
(This mindset very different to OO which involves
distributing state across objects, and then ensuring that state is synchronized, all the while
trying to hide it, which is, when you think about it, quite crazy ... and I did it for years).
2. Because all app state is coalesced into one atom, it can be updated
with a single `reset!`, which acts like a transactional commit. There is
an instant in which the app goes from one state to the next, never a series
of incremental steps which can leave the app in a temporarily inconsistent, intermediate state.
Again, this simplicity causes a certain class of bugs or design problems evaporate.
3. The data in `app-db` can be given a strong schema
so that, at any moment, we can validate all the data in the application. **All of it.**
We do this check after every single "event handler" runs (event handlers compute new state).
And this enables us to catch errors early (and accurately). It increases confidence in the way
that Types can increase confidence, only [a good schema can provide more
**leverage** than types](https://www.youtube.com/watch?v=nqY4nUMfus8).
4. Undo/Redo [becomes straight forward to implement](https://github.com/Day8/re-frame-undo).
It is easy to snapshot and restore one central value. Immutable data structures have a
feature called `structural sharing` which means it doesn't cost much RAM to keep the last, say, 200
snapshots. All very efficient.
For certain categories of applications (eg: drawing applications) this feature is borderline magic.
Instead of undo/redo being hard, disruptive and error prone, it becomes virtually trivial.
**But,** many web applications are not self contained
data-wise and, instead, are dominated by data sourced from an authoritative remote database.
For these applications, re-frame's `app-db` is mostly a local caching
point, and being able to do undo/redo its state is meaningless because the authoritative
source of data is elsewhere.
5. The ability to genuinely model control via FSMs (discussed later)
6. The ability to do time travel debugging, even in a production setting. More soon.

View File

View File

@ -1,145 +1,4 @@
[logo](/images/logo/re-frame_512w.png?raw=true)
## Derived Values, Flowing
> This, milord, is my family's axe. We have owned it for almost nine hundred years, see. Of course,
sometimes it needed a new blade. And sometimes it has required a new handle, new designs on the
metalwork, a little refreshing of the ornamentation . . . but is this not the nine hundred-year-old
axe of my family? And because it has changed gently over time, it is still a pretty good axe,
y'know. Pretty good.
> -- Terry Pratchett, The Fifth Elephant <br>
> &nbsp;&nbsp;&nbsp; Reflecting on time, identity, flow and derived values
## Why Should You Care?
Perhaps:
1. You want to develop an [SPA] in ClojureScript, and you are looking for a framework
2. You believe that Facebook did something magnificent when it created React, and
you are curious about the further implications. Is the combination of
`reactive programming`, `functional programming` and `immutable data` going to
**completely change everything**? And, if so, what would that look like in a language
that embraces those paradigms?
3. You're taking a [Functional Design and Programming course at San Diego State University](http://www.eli.sdsu.edu/courses/fall15/cs696/index.html)
and you have to learn re-frame to do an assignment. You've left it a bit late, right?
Good news, there is a quick start guide shortly.
4. You demand social proof in your frameworks!!! Luckily, re-frame is impressively
buzzword compliant: it has reactivity, unidirectional data flow, pristinely pure functions,
interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)
and claims an immaculate hammock conception. It also has a charming
xkcd reference (soon) and a hilarious, insiders-joke T-shirt,
ideal for conferences (in design). What could possibly go wrong?
## re-frame
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
This repo contains both a **description of this pattern** and
a **reference implementation**.
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
Because it is a functional framework, you program it by:
- designing data and
- writing pure functions which transform this data
### It is a loop
Architecturally, re-frame implements "a perpetual loop".
To build an app, you hang functions on certain parts of this loop,
and re-frame looks after the `conveyance of data` (flow of data)
around the loop, into and out of the transforming functions you
provide - hence the tag line "Derived Data, Flowing".
### With 5 dominoes
Computationally, each iteration of the loop involves the same
5 domino cascade. One domino triggering the next, which triggers the next, etc,
until we are back at the beginning of the loop. Each iteration has the same cascade.
An `event` acts as the **1st domino**.
An event might be user initiated,
like "delete button clicked", or it might be initiated by another outside agent, like "a websocket
delivered a document". Without the impulse of a triggering `event`, no 5 domino cascade occurs.
So, it is only because of `events` that a re-frame app is propelled, loop iteration after loop iteration,
from one state to the next.
re-frame is `event` driven.
The **2nd domino** is `event handling` which involves computing how the application should
respond/change to the new `event` occurrence.
Event handlers produce `effects` or, more accurately,
a **description** of `effects`. These descriptions say how the state of
an SPA itself should change, and sometimes they also say how the outside world should change
(localstore, cookies, databases, emails, etc).
The **3rd domino** takes these descriptions (of `effects`) and actions them. Makes them real.
Now, to a functional programmer, `effects` are scary, in a [xenomorph kind of way](https://www.google.com.au/search?q=xenomorph).
Nothing messes with functional purity
quite like the need for effects and coeffects. But, on the other hand, `effects` are equally
marvelous because they take the app forward. Without them, an app stays stuck in one state forever,
never achieving anything.
So re-frame embraces the protagonist nature of `effects` - the entire, unruly zoo of them - but
it does so in a controlled, debuggable, auditable, mockable, plugable way.
After the effectful 3rd domino handlers have run,
something about the world will have changed, often the app's state. **Dominoes 4 and 5** close
the re-frame loop by re-rendering the UI to reflect any change in application state.
These two dominoes combine to implement the formula made famous by React: `v = f(s)` - a view `v`
is a function `f` of the app state `s`. **Over time**, when `s` changes, `f`
must be called again to compute new `v`.
Given domino 3 changed application state, `s`, dominos 4 and 5 must conceptually re-run `f` so as
to produce a new `v` which shows this change.
The mechanics of this are as follows (do not be alarmed by the terminology): domino 4 is a
de-duplicated signal graph which reactivity queries
application state, while domino 5 involves the reactive, declarative rendering
of views using React/Reagent. You'll see nice simple code in a minute.
### A Dominoes Walk Through
Imagine the following scenario: the user clicks the delete button
for the 3rd item in a list. In response, what happens within a re-frame app?
The 5 domino cascade:
1. The click handler for that button uses the re-frame supplied function, `dispatch`,
to send an `event`, which might look like this `[:delete-item 2]`. Yes, that's a vector of two elements.
2. The `event handler` (function) associated with `:delete-item` (the first element of the event)
is called to compute what the `effect` of the `event` should be. In this case, it computes
that new application state should result (this state will not include the deleted item).
3. an `effect handler` (function) resets application state to the newly computed value.
4. a query (function) over the application state is called (reactively), computing a new
result (containing no 3rd item!).
5. a view (function) is called to re-compute DOM (reactively), because the query state to which
it is subscribed has changed. (Thereafter, React invisibly does the dirty work
of actually mutating the DOM).
At this point, the re-frame app returns to a quiescent state, waiting for the next event. When it comes, a
similar 5 domino cascade will happen again.
### A Simple Loop Of Simple Functions
**Each of these dominoes are simple, pure functions** which can be be described, understood and
tested independently (other than domino 3). They take data, transform it and return new data.
The loop itself is utterly predictable and 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 a great ease in reasoning and debugging.
**At this point you know 50% of re-frame.** We still have to fill in some detail, of course,
but the core concepts are known to you.
### Some Code
@ -156,34 +15,6 @@ but the core concepts are known to you.
At this point you need to see some code ... XXXXXX
## Data Oriented Design
You might already know that ClojureScript is a modern lisp, and that
lisps are **homoiconic**. If not, you do now.
The homoiconic bit is significant. It means you program in a lisp by creating and
assembling lisp data structures. So you are **programming in data**.
The functions which later manipulate data, start as data.
Clojure programmers place particular
emphasis on the primacy of data. When they aren't re-watching Rich Hickey videos,
and wishing their hair was darker and more curly,
they meditate on aphorisms like "Data is the ultimate in late binding"
and "data > functions > macros".
I cannot stress too much what a big deal this is. It might seem
just a syntax curiosity at first but, when the penny drops for
you on this, it tends to be a profound moment. And once you
understand the importance of this concept at the language level,
you naturally want a similar approach at the library level.
So, it will come as no surprise, then, to know that re-frame has a
data oriented design. Events are data. Effects are data. DOM is data.
The functions which transform data are registered and looked up via
data. Interceptors (data) are preferred over middleware (higher
order functions). Etc.
Data - that's the way we roll.
## Finite State Machines
@ -214,24 +45,6 @@ So, in this section, I'm suggesting that this perspective is useful sometimes:
Events - that's the way we roll.
### It is Mature
re-frame was released early 2015, and it has subsequently been
used by a number of companies and individuals to build complex apps.
Frameworks
are just pesky overhead at small scale - measure them instead by how they help
you tame the complexity of bigger apps, and in this regard re-frame seems to have
worked out well. Some have even praised it effusively.
Having said that, re-frame remains a work in progress and it falls
short in a couple of ways - for example it doesn't work as well as we
want with devcards - we're still
puzzling over those aspects and we continue tweaking as we go.
And, yes, it is fast, straight out of the box. And, yes, it has
a good testing story. And, yes, it works in with figwheel to create
a delightful live-coding development story.
## It Does Physics
@ -255,14 +68,6 @@ this diagram, apart from being a plausible analogy, and bordering on useful,
is practically PROOF that re-frame is doing physics.
[![Clojars Project](https://img.shields.io/clojars/v/re-frame.svg)](https://clojars.org/re-frame)
[![GitHub license](https://img.shields.io/github/license/Day8/re-frame.svg)](license.txt)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master)
[![Sample Project](https://img.shields.io/badge/project-example-ff69b4.svg)](https://github.com/Day8/re-frame/tree/master/examples)
__Warning__: That was the summary. What follows is a long-ish tutorial/explanation.
## Tutorial Table of Contents
@ -364,112 +169,10 @@ introductory Reagent tutorials before going on. Try:
- [Building Single Page Apps with Reagent](http://yogthos.net/posts/2014-07-15-Building-Single-Page-Apps-with-Reagent.html).
## Explaining re-frame
To explain re-frame, I'll incrementally develop a diagram, describing each part as it is added.
### 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>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
### The Big Ratom
Our re-frame diagram starts (very modestly) with Fogus' ***well-formed data at rest*** bit:
```
app-db
```
re-frame says to put all your application state into one place, which we'll
call `app-db`. Ideally, you will also provide a spec for this data in the one place,
[using a powerful and leveragable schema](http://clojure.org/about/spec).
Now, this advice is not the slightest bit controversial for 'real' databases, right?
You'd happily put all your well-formed data into PostgreSQL.
But within a running application (in memory), there is hesitation. If you have
a background in OO, this data-in-one-place
business is a really, really hard one to swallow. You've
spent your life breaking systems into pieces, organised around behaviour and trying
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 perfect.
From here on in this document, we'll assume `app-db` is one of these:
```clj
(def app-db (reagent/atom {})) ;; a Reagent atom, containing a map
```
Although it is a `Reagent atom` (hereafter `ratom`), I'd encourage
you to think of it as an in-memory database. It will contain structured data.
You will need to query that data. You will perform CRUD
and other transformations on it. You'll often want to transact on this
database atomically, etc. So "in-memory database"
seems a more useful paradigm than plain old map-in-atom.
Further Notes:
1. `app-state` would probably be a more accurate name, but I choose `app-db` instead because
I wanted to convey the database notion as strongly as possible.
2. In the documentation and code I make the distinction between `app-db` (the `ratom`) and
`db` which is the (map) `value` currently stored **inside** the `ratom`. It will help with
3. the reference implementation creates and manages an `app-db` for you, so
you don't need to declare one yourself (see the 1st FAQ if you want to inspect the values in it).
your reading of the material if you keep this distinction in mind as we go forward.
4. `app-db` doesn't actually have to be a `ratom` containing a map. It could, for example,
be a [datascript] database. In fact, any database which is reactive
(can tell you when it changes) would do. We'd love! to be using [datascript] - 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).
### The Benefits Of Data-In-The-One-Place
1. Here's the big one: because there is a single source of truth, we write no
code to synchronizes state between many different stateful components. I
cannot stress too much how significant this is. You end up writing less code
and an entire class of bugs is eliminated. The simplicity it brings is breathtaking.
(This mindset very different to OO which involves
distributing state across objects, and then ensuring that state is synchronized, all the while
trying to hide it, which is, when you think about it, quite crazy ... and I did it for years).
2. Because all app state is coalesced into one value, it can be updated
with a single `reset!` operation, which acts like a transactional commit. There is
an instant in which the app goes from one state to the next, never a series
of incremental steps which can leave the app in a temporarily inconsistent, intermediate state.
Again, this simplicity caused a certain class of bugs or design problems evaporate.
3. The data in `app-db` can be given a strong schema
so that, at any moment, we can validate all the data in the application. **All of it.**
We do this check after every single "event" is processed (events are what "change" state).
And this enables us to catch errors early (and accurately). It increases confidence in the way
that Types can increase confidence, only [a good schema can provide more
**leverage** than types](https://www.youtube.com/watch?v=nqY4nUMfus8).
4. Undo/Redo [becomes straight forward to implement](https://github.com/Day8/re-frame-undo).
It is easy to snapshot and restore one central value. Immutable data structures have a
feature called `structural sharing` which means it doesn't cost much RAM to keep the last, say, 200
snapshots. All very efficient.
For certain categories of applications (eg: drawing applications) this feature is borderline magic.
Instead of undo/redo being hard, disruptive and error prone, it becomes virtually trivial.
**But,** many web applications are not self contained
data-wise and, instead, are dominated by data sourced from an authoritative remote database.
For these applications, re-frame's `app-db` is mostly a local caching
point, and being able to do undo/redo its state is meaningless because the authoritative
source of data is elsewhere.
5. The ability to genuinely model control via FSMs (discussed later)
6. The ability to do time travel debugging, even in a production setting. More soon.
## Flow
Back in the introduction, I explained that re-frame implements a loop and that each iteration involves a
5-domino-cascade.
Now we're discussed We're going to discuss dominoes 4 and 5.
Arguments from authority ...
> Everything flows, nothing stands still. (Panta rhei)
@ -1070,27 +773,6 @@ could almost imagine them as a "stored procedures" on a
database. Almost. Stretching it? We do like our in-memory
database analogies.
### What are events?
Events are data. You choose the format.
In our reference implementation we choose a vector format. For example:
[:delete-item 42]
The first item in the vector identifies the event and
the rest of the vector is the optional parameters -- in the example above, the id (42) of the item to delete.
Here are some other example events:
```Clojure
[:yes-button-clicked]
[:set-spam-wanted false]
[[:complicated :multi :part :key] "a parameter" "another one" 45.6]
```
**Rule**: events are pure data. No dirty tricks like putting callback functions on the wire.
You know who you are.
### Dispatching Events
@ -1162,11 +844,6 @@ issues like Middleware [in the Wiki](https://github.com/Day8/re-frame/wiki#handl
### Routing
When `dispatch` is passed an event vector, it just puts that event onto a conveyor belt.
The consumer on the end of the conveyor is a `router` which will organise for that
event to be processed by the right handler.
```
app-db --> components --> Hiccup --> Reagent --> VDOM --> React --> DOM
@ -1317,39 +994,7 @@ This is utterly, utterly perfect for debugging assuming, of course, you are in a
a checkpoint, and the events since then.
### In Summary
re-frame has two distinct flows, and I claim they are BOTH FRP in nature. The first is clearly FRP.
The second one is conceptually FRP, but you do have to squint.
All the parts are simple. The parts are easy to understand in isolation. The parts are composed so that
derived data flows in a perpetual reactive loop, through pure functions.
To build an app using re-frame, you'll have to:
- design your app's data structure.
- write and register subscription functions (query layer).
- write component functions (view layer).
- write and register event handler functions (control layer and/or state transition layer).
### Where Do I Go Next?
Next:
- look at the examples: https://github.com/Day8/re-frame/tree/master/examples
- read the docs: https://github.com/Day8/re-frame/blob/master/docs/README.md
Then:
- use the lein template: https://github.com/Day8/re-frame-template
Also, if you want reusable layout and widget components, consider this sister project:
https://github.com/Day8/re-com
### Licence
Copyright © 2015 Michael Thompson
Distributed under The MIT License (MIT) - See LICENSE.txt
[SPAs]:http://en.wikipedia.org/wiki/Single-page_application
[SPA]:http://en.wikipedia.org/wiki/Single-page_application
@ -1361,6 +1006,5 @@ Distributed under The MIT License (MIT) - See LICENSE.txt
[Elm]:http://elm-lang.org/
[OM]:https://github.com/swannodette/om
[Prismatic Schema]:https://github.com/Prismatic/schema
[datascript]:https://github.com/tonsky/datascript
[Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app

224
docs/WIP/README.md Normal file
View File

@ -0,0 +1,224 @@
[logo](/images/logo/re-frame_128w.png?raw=true)
## Derived Values, Flowing
> This, milord, is my family's axe. We have owned it for almost nine hundred years, see. Of course,
sometimes it needed a new blade. And sometimes it has required a new handle, new designs on the
metalwork, a little refreshing of the ornamentation ... but is this not the nine hundred-year-old
axe of my family? And because it has changed gently over time, it is still a pretty good axe,
y'know. Pretty good.
> -- Terry Pratchett, The Fifth Elephant <br>
> &nbsp;&nbsp;&nbsp; Reflecting on identity, flow and derived values
[![Clojars Project](https://img.shields.io/clojars/v/re-frame.svg)](https://clojars.org/re-frame)
[![GitHub license](https://img.shields.io/github/license/Day8/re-frame.svg)](license.txt)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master)
## Why Should You Care?
Perhaps:
1. You want to develop an [SPA] in ClojureScript, and you are looking for a framework
2. You believe that Facebook did something magnificent when it created React, and
you are curious about the further implications. Is the combination of
`reactive programming`, `functional programming` and `immutable data` going to
**completely change everything**? And, if so, what would that look like in a language
that embraces those paradigms?
3. You're taking a [Functional Design and Programming course at San Diego State University](http://www.eli.sdsu.edu/courses/fall15/cs696/index.html)
and you have to learn re-frame to do an assignment. You've left it a bit late, right?
Good news, there is a quick start guide shortly.
4. You want your framework to exude social proof!! Luckily, re-frame is impressively
buzzword compliant: it has reactivity, unidirectional data flow, pristinely pure functions,
interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)
and claims an immaculate hammock conception. It also has a charming
xkcd reference (soon) and a hilarious, insiders-joke T-shirt,
ideal for conferences (in design). What could possibly go wrong?
## re-frame
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
This repo contains both a **description of this pattern** and
a **reference implementation**.
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
Being a functional framework, you program it by:
- designing data and
- writing pure functions which transform this data
### It Is A Loop
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` (flow of data)
around the loop, into and out of the transforming functions you
provide - hence the tag line "Derived Data, Flowing".
### It Has 5 Dominoes
Computationally, each iteration of the loop involves the same
5 domino cascade. One domino triggering the next, which triggers the next, etc,
until we are back at the beginning of the loop. Each iteration has the same cascade.
<img style="float: right;" src="/images/readme/dominos.jpg">
An `event` acts as the **1st domino**.
An event might be initiated by a user clicking a button, or entering a field,
or it might be initiated by another agent, like "a websocket receiving a packet".
Without the impulse of a triggering `event`, no 5 domino cascade occurs.
So, it is only because of `events` that a re-frame app is propelled, loop iteration after loop iteration,
from one state to the next.
re-frame is `event` driven.
The **2nd domino**, `event handling`, involves computing how the application should
respond/change to the new `event` occurrence.
Event handlers produce `effects` or, more accurately,
a **description** of `effects`. These descriptions say how the state of
an SPA itself should change, and sometimes they also say how the outside world should change
(localstore, cookies, databases, emails, etc).
The **3rd domino** takes these descriptions (of `effects`) and actions them. Makes them real.
Now, to a functional programmer, `effects` are scary, in a [xenomorph kind of way](https://www.google.com.au/search?q=xenomorph).
Nothing messes with functional purity
quite like the need for effects and coeffects. But, on the other hand, `effects` are equally
marvelous because they take the app forward. Without them, an app stays stuck in one state forever,
never achieving anything.
So re-frame embraces the protagonist nature of `effects` - the entire, unruly zoo of them - but
it does so in a controlled, debuggable, auditable, mockable, plugable way.
After the effectful 3rd domino handlers have run,
something about the world will have changed, often the app's state. **Dominoes 4 and 5** close
the re-frame loop by re-rendering the UI to reflect any change in application state.
These two dominoes combine to implement the formula made famous by React: `v = f(s)` - a view `v`
is a function `f` of the app state `s`. **Over time**, when `s` changes, `f`
will be called again to compute new `v`, forever keeping `v` up to date with the current `s`.
Given domino 3 changed application state, `s`, dominos 4 and 5 are about re-running `f` so as
to produce a new `v` which shows this change.
The mechanics of this are as follows (do not be alarmed by the terminology): domino 4 is a
de-duplicated signal graph which reactivity queries
application state, while domino 5 involves the reactive, declarative rendering
of views using React/Reagent. You'll see nice simple code in a minute.
### A Dominoes Walk Through
Imagine the following scenario: the user clicks the delete button
for the 3rd item in a list. In response, what happens within a re-frame app?
The 5 domino cascade:
1. The click handler for that button uses the re-frame supplied function, `dispatch`,
to send an `event`, which might look like this `[:delete-item 2]`. Yes, that's a vector of two elements.
2. The `event handler` (function) associated with `:delete-item` (the first element of the event)
is called to compute what the `effect` of the `event` should be. In this case, it computes
that new application state should result (this state will not include the deleted item).
3. an `effect handler` (function) resets application state to the newly computed value.
4. a query (function) over the application state is called (reactively), computing a new
result (containing no 3rd item!).
5. a view (function) is called to re-compute DOM (reactively), because the query state to which
it is subscribed has changed.
At this point, the re-frame app returns to a quiescent state, waiting for the next event. When one comes, a
similar 5 domino cascade will happen again.
### A Simple Loop Of Simple Functions
**Each of these dominoes are simple, pure functions** which can be be described, understood and
tested independently (other than domino 3). They take data, transform it and return new data.
The loop itself is utterly predictable and 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 a great ease in reasoning and debugging.
**At this point you know 50% of re-frame.** Sure, there's some detail to fill in,
but the core concepts are known to you.
## It Leverages Data
You might already know that ClojureScript is a modern lisp, and that
lisps are **homoiconic**. If not, you do now.
The homoiconic bit is significant. It means you program in a lisp by creating and
assembling lisp data structures. So you are **programming in data**.
The functions which later manipulate data, start as data.
Clojure programmers place particular
emphasis on the primacy of data. When they aren't re-watching Rich Hickey videos,
and wishing their hair was darker and more curly,
they meditate on aphorisms like "Data is the ultimate in late binding"
and "data > functions > macros".
I cannot stress too much what a big deal this is. It might seem
just a syntax curiosity at first but, when the penny drops for
you on this, it tends to be a profound moment. And once you
understand the importance of this concept at the language level,
you naturally want a similar approach at the library level.
So, it will come as no surprise, then, to know that re-frame has a
data oriented design. Events are data. Effects are data. DOM is data.
The functions which transform data are registered and looked up via
data. Interceptors (data) are preferred over middleware (higher
order functions). Etc.
Data - that's the way we roll.
### It is Mature
re-frame was released early 2015, and it has subsequently been
used by a number of companies and individuals to build complex apps.
Frameworks
are just pesky overhead at small scale - measure them instead by how they help
you tame the complexity of bigger apps, and in this regard re-frame seems to have
worked out well. Some have even praised it effusively.
Having said that, re-frame remains a work in progress and it falls
short in a couple of ways - for example it doesn't work as well as we
want with devcards - we're still
puzzling over those aspects and we continue tweaking as we go.
And, yes, it is fast, straight out of the box. And, yes, it has
a good testing story. And, yes, it works in with figwheel to create
a delightful live-coding development story.
### Where Do I Go Next?
Want to see code? Want more mental models? Read the docs:
https://github.com/Day8/re-frame/blob/master/docs/README.md
Look at the examples:
https://github.com/Day8/re-frame/tree/master/examples
Use a template to create your own project:
https://github.com/Day8/re-frame-template
Use these resources:
https://github.com/Day8/re-com
XXX
### Licence
Copyright © 2015 Michael Thompson
Distributed under The MIT License (MIT) - See LICENSE.txt
[SPAs]:http://en.wikipedia.org/wiki/Single-page_application
[SPA]:http://en.wikipedia.org/wiki/Single-page_application
[Reagent]:http://reagent-project.github.io/

View File

@ -12,7 +12,7 @@
<script src="js/client.js"></script>
<script>
window.onload = function () {
simpleexample.core.run();
simple.core.run();
}
</script>
</body>

View File

@ -0,0 +1,83 @@
(ns simple.core
(:require [reagent.core :as reagent]
[re-frame.core :as rf]))
;; -- Domino 1 - Event Dispatch -----------------------------------------------
(defn dispatch-timer-event
[]
(let [now (js/Date.)]
(rf/dispatch [:timer now])))
;; an event will be dispatched every second
(defonce time-updater (js/setInterval dispatch-timer-event 1000))
;; -- Domino 2 - Event Handlers -----------------------------------------------
(rf/reg-event-db ;; sets up initial application state
:initialize ;; usage: (dispatch [:initialize])
(fn [_ _] ;; the two parameters are not important here, so use _
{:time (js/Date.) ;; What it returns becomes the new application state
:time-color "#f88"})) ;; so the application state will initially be a map with two keys
(rf/reg-event-db ;; usage: (dispatch [:time-color-change 34562])
:time-color-change ;; dispatched when the user enters a new colour into the UI
(fn [db [_ new-color-value]] ;; -db event handlers given 2 parameters: current application state and event (a vector)
(assoc db :time-color new-color-value))) ;; compute and return the new application state
(rf/reg-event-db ;; usage: (dispatch [:timer a-js-Date])
:timer ;; every second an event of this kind will be dispatched
(fn [db [_ new-time]] ;; note how the 2nd parameter is desctructure to obtain the data value
(assoc db :time new-time))) ;; compute and return the new application state
;; -- Domino 4 - Query -------------------------------------------------------
(rf/reg-sub
:time
(fn [db _] ;; db is current app state. 2nd usused param is query vector
(:time db))) ;; return a query computation over the application state
(rf/reg-sub
:time-color
(fn [db _]
(:time-color db)))
;; -- Domino 5 - View Functions ----------------------------------------------
(defn clock
[]
[:div.example-clock
{:style {:color (rf/listen [:time-color])}}
(-> (rf/listen [:time])
.toTimeString
(clojure.string/split " ")
first)])
(defn color-input
[]
[:div.color-input
"Time color: "
[:input {:type "text"
:value (rf/listen [:time-color])
:on-change #(rf/dispatch [:time-color-change (-> % .-target .-value)])}]]) ;; <---
(defn ui
[]
[:div
[:h1 "Hello world, it is now"]
[clock]
[color-input]])
;; -- Entry Point -------------------------------------------------------------
(defn ^:export run
[]
(dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into '<div id="app" />'
(js/document.getElementById "app")))

View File

@ -1,196 +0,0 @@
(ns simpleexample.core
(:require-macros [reagent.ratom :refer [reaction]])
(:require [reagent.core :as reagent]
[re-frame.core :as rf]))
;; Read this file from the top towards the bottom.
;; Normally I'd recomended you write a schema for the appplication
;; state. But I'm going to skip that step to keep things simple.
;;
;; Suffice it to say the application state looks like this:
;; {:time (js/Date.)
;; :time-color "#f88"}
;;
;; See FAQ #1 for details on how to inspect application state.
;; -- Domino 1 - Events -------------------------------------------------------
;;
;; To send an event you call `dispatch` with a single vector argument.
;;
;; (dispatch [:event-id value1 value2])
;;
;; The first element in the vector identifies the kind of event. The
;; further elements are optional, additional data associated with the event.
;;
;; Below we send a `:timer` event every second. But, typically,
;; events are `dispatch`ed when a button is clicked or an
;; HTTP POST's on-success is called or a web sockets gets a new packet.
;;
;; So `dispatch` gets called in various places.
;;
;; Later, when we get to Domino 5 (view functions) you'll see
;; an example of DOM elements causing a `dispatch`.
(defn dispatch-timer-event
[]
(let [now (js/Date.)]
(rf/dispatch [:timer now])))
;; an event will be dispatched every second
(defonce time-updater (js/setInterval dispatch-timer-event 1000))
;; -- Domino 2 - Event Handlers -----------------------------------------------
;;
;; Associate a "handler function" with each kind of event.
;;
;; This application has three events, identified by keywords:
;; :initialise
;; :time-color-change
;; :timer
;;
;; Because there's 3 events, below you'll see `reg-event-db`
;; called 3 times, registering 3 handlers, each like this:
;; (reg-event-db
;; :a-keyword-id-for-the-event
;; a-function-which-will-handle-the-event)
;;
;; Any event handler registered via `reg-event-db` will look like this:
;; (fn [db v]
;; ...)
;; It will take two parameters, the current application state `db`
;; and the event vector `v` which is being handled.
;; It must compute and return the new state of the application.
;;
;; Below, you'll sometimes see it written as:
;; (fn [db [_ something]] <-- 2nd vec param destructured to get payload value
;; ...)
;;
;; An event handler return `effects`. We're using
;; `reg-event-db` here which assumes the only effect required (for this event)
;; is a change to application state. When you need to do other effects,
;; like sending emails, or http POSTing, or writing to localstore, you
;; would use the more sophisticated `reg-event-fx` (note the trailing -fx) which
;; is described in the tutorial. We're keeping it simple for the moment.
(rf/reg-event-db ;; sets up initial application state
:initialize ;; usage: (dispatch [:initialize])
(fn [_ _] ;; the two parameters are not important here, so use _
{:time (js/Date.) ;; What it returns becomes the new application state
:time-color "#f88"})) ;; so the application state will initially be a map with two keys
(rf/reg-event-db ;; usage: (dispatch [:time-color-change 34562])
:time-color-change ;; dispatched when the user enters a new colour into the UI
(fn [db [_ new-color-value]] ;; -db event handlers given 2 parameters: current application state and event (a vector)
(assoc db :time-color new-color-value))) ;; compute and return the new application state
(rf/reg-event-db ;; usage: (dispatch [:timer a-js-Date])
:timer ;; every second an event of this kind will be dispatched
(fn [db [_ new-time]] ;; note how the 2nd parameter is desctructure to obtain the data value
(assoc db :time new-time))) ;; compute and return the new application state
;; -- Domino 3 - Efect Handlers -----------------------------------------------
;;
;; You almost never have to write effect handlers. You just use the standard
;; re-frame ones or, sometimes, ones from a library.
;;
;; In this simple app, the only effect returned by event handlers is that
;; of changing application state, which is a standard effect, and even THAT
;; effect is implied (rather than explicit) because we are using the
;; helpful registration function `reg-event-db`
;;
;; Just know that effects are happening but we don't need to do anything. We'll
;; learn more about effect handlers later.
;; -- Domino 4 - Subscription Handlers ----------------------------------------
;;
;; These query functions take application state and return a "materialised view"
;; of that state. A computation over the current application state.
;;
;; Now, the examples below are trivial. They just extract part of the application
;; state and return it. Virtually no computation.
;;
;; More interesting subscriptions and more explanation can be found in the
;; todomvc example.
;;
;; `reg-sub` associates a `query identifier` with a function which computes
;; that query, like this:
;; (reg-sub
;; :some-query-id ;; query identifier
;; some-function) ;; the function which will compute the query
;; If ever a view requests data like this:
;; `(listen [:some-query-id])` ;; note use of `:some-query-id`
;; then `some-function` will be used to perform the query over applciation state
;; when a
;;
;; Each time application state changes, `some-function` will be
;; called again to compute a new materialised view (a new computation)
;; 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 create new DOM (because it depends on a query value which changed).
;;
;; re-frame will ensure the necessary calls are made, at the right time.
;;
(rf/reg-sub
:time
(fn [db _] ;; db is current app state. 2nd usused param is query vector
(:time db))) ;; return a query computation over the application state
(rf/reg-sub
:time-color
(fn [db _]
(:time-color db)))
;; -- Domino 5 - View Functions ----------------------------------------------
;;
;; We're using Reagent here.
;; These functions turn data into DOM. They render the UI.
;; The data they return is a hiccup formated description of the DOM required.
;; They take two sources of data:
;; - arguments
;; - queries which obtain data from the application state.
;;
;;
;; Noitce that color-input has a dispatch
(defn clock
[]
[:div.example-clock
{:style {:color (rf/listen [:time-color])}}
(-> (rf/listen [:time])
.toTimeString
(clojure.string/split " ")
first)])
(defn color-input
[]
[:div.color-input
"Time color: "
[:input {:type "text"
:value (rf/listen [:time-color])
:on-change #(rf/dispatch [:time-color-change (-> % .-target .-value)])}]]) ;; <---
(defn ui
[]
[:div
[:h1 "Hello world, it is now"]
[clock]
[color-input]])
;; -- Entry Point -------------------------------------------------------------
(defn ^:export run
[]
(dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into '<div id="app" />'
(js/document.getElementById "app")))

BIN
images/Readme/Dominos.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB