426 lines
17 KiB
Markdown
426 lines
17 KiB
Markdown
[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>
|
|
> 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 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 a re-frame/reagent assignment due. You've left the reading a bit late, right? I remember those days.
|
|
4. 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 damn trouble maker, why even mention an OO pattern?
|
|
re-frame is a **functional framework**."
|
|
|
|
Being a functional framework, it is about data, and the pure functions
|
|
which transform that 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 does Physics
|
|
|
|
Remember this diagram from school? The water cycle, right?
|
|
|
|
<img height="400px" align="right" src="/images/the-water-cycle.png?raw=true">
|
|
|
|
Two distinct stages, involving water in different phases, being acted upon
|
|
by different forces: gravity working one way, evaporation/convection the other.
|
|
|
|
To understand re-frame, **imagine data flowing around that loop instead of water**. re-frame
|
|
provides the "conveyance" of the data - the equivalent of gravity, evaporation and convection.
|
|
You design what's flowing and then you hang functions off the loop at
|
|
various points to compute the data's phase changes.
|
|
|
|
Sure, right now, you're thinking "lazy sod - make a proper Computer Science-y diagram". But, no.
|
|
Joe Armstrong says "don't break the laws of physics" - I'm sure
|
|
you've seen the videos - and if he says to do something, you do it
|
|
(unless Rich Hickey disagrees, and says to do something else). So,
|
|
this diagram, apart from being a plausible analogy which might encourage
|
|
you to look differently at re-frame, is **practically proof** it does physics.
|
|
|
|
### It is a 6-domino cascade
|
|
|
|
Computationally, each iteration of the loop involves a
|
|
6 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 align="right" src="/images/Readme/Dominoes-small.jpg?raw=true">
|
|
|
|
#### 1st Domino
|
|
|
|
An `event` is sent when something happens - the user
|
|
clicks a button, or a websocket receives a new message.
|
|
|
|
Without the impulse of a triggering `event`, no 6 domino cascade occurs.
|
|
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.
|
|
|
|
#### 2nd Domino
|
|
|
|
In response to an `event`, an application must compute
|
|
the implication (the ambition, the intent). This is known as `event handling`.
|
|
|
|
Event handler functions compute `effects`. Or, more accurately, they compute
|
|
a **description of `effects`**, which means they say, declaratively,
|
|
how the world should change (because of the event).
|
|
|
|
Much of the time, only the state of the SPA itself need
|
|
change, but sometimes the outside world must too must be effected
|
|
(localstore, cookies, databases, emails, logs, etc).
|
|
|
|
#### 3rd Domino
|
|
|
|
These descriptions of `effects` are actioned. The intent is realised.
|
|
|
|
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.
|
|
|
|
#### Then what happens?
|
|
|
|
So, that 3rd domino just changed the world and, very often, that involves
|
|
one particular part of the world, namely the **app's internal state**.
|
|
|
|
re-frame `app state` is held in one place - think of it like you
|
|
would an in-memory, central database for the app.
|
|
|
|
When domino 3 changes `app state`, it triggers the next part of the cascade
|
|
involving dominoes 4-5-6.
|
|
|
|
#### The view formula
|
|
|
|
The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library:
|
|
`v = f(s)`
|
|
A view `v` is a function `f` of the app state `s`.
|
|
|
|
Or, said another way, there are functions `f` which compute what DOM nodes, `v`,
|
|
should be displayed to the user when the application is in a given app state, `s`.
|
|
|
|
Or, another way: **over time**, when `s` changes, `f`
|
|
will be called again to compute new `v`, forever keeping `v` up to date with the current `s`.
|
|
|
|
Now, in our case, it is domino 3 which changes `s`, the application state,
|
|
and, in response, dominoes 4-5-6 are about re-running `f` to compute the new `v`
|
|
shown to the user.
|
|
|
|
Except, of course, there's nuance. For instance, there's no single `f` to run.
|
|
There may be many functions which collectively build the overall DOM,
|
|
and only part of `s` may change at any one time, so only part of the
|
|
`v` (DOM) need be re-computed and updated. And some parts of `v` might not
|
|
even be showing right now.
|
|
|
|
#### Domino 4
|
|
|
|
Domino 4 is a novel and efficient de-duplicated signal graph which
|
|
runs query functions on the app state, `s`, efficiently computing
|
|
reactive, multi-layered, "materialised views" of `s`.
|
|
|
|
(Relax about any unfamiliar terminology, you'll soon
|
|
see how simple the code actually is)
|
|
|
|
#### Domino 5
|
|
|
|
Domino 5 is one or more **view functions** (aka Reagent components) which compute what
|
|
UI DOM should be displayed for the user.
|
|
|
|
They take data, delivered reactively by the queries of domino 4,
|
|
and compute hiccup-formatted data, which is a description of the DOM required.
|
|
More on hiccup soon.
|
|
|
|
#### Domino 6
|
|
|
|
Domino 6 is not something you need write yourself - instead it is handled for you
|
|
by Reagent/Rect. I mention it here
|
|
for completeness and to fully close the loop.
|
|
|
|
This is the step in which the hiccup-formatted
|
|
"descriptions of required DOM", returned by Domino 5, are made real. The
|
|
browser DOM nodes are mutated.
|
|
|
|
### A Simple Loop Of Simple Functions
|
|
|
|
**Each of the dominoes you supply are simple, pure functions** which
|
|
can be be described, understood and
|
|
tested independently. They take data, transform it and return new data.
|
|
|
|
The loop itself is utterly predictable and very mechanical in operation.
|
|
So, there's a regularity, simplicity and
|
|
certainty to how a re-frame app goes about its business,
|
|
which leads, in turn, to an ease in reasoning and debugging.
|
|
|
|
## Managing mutation
|
|
|
|
The two sub-cascades 1-2-3 and 4-5-6 have a similar structure.
|
|
|
|
In each cascade, it is the 2nd to last domino which
|
|
computes "descriptions" of mutations required and it is
|
|
the last domino which actions these descriptions - it does the dirty work.
|
|
|
|
And in both case, you need worry yourself about this dirty work. re-frame looks
|
|
after those dominoes.
|
|
|
|
## Code Fragments
|
|
|
|
Time to understand this
|
|
dominoes narrative in terms of code fragments.
|
|
|
|
> At this point, we're moving from 30,000 feet to say 30 feet. I'm not expecting you
|
|
to fully grok all the code presented. We're still in overview mode here and there's not
|
|
a lot of detail given. Plenty of tutorials and examples to follow.
|
|
|
|
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.
|
|
|
|
In response,
|
|
what happens within this imaginary re-frame app? Here's a sketch of the 6 domino cascade:
|
|
|
|
#### Code For Domino 1
|
|
|
|
1. The delete button for that 3rd item will have an `on-click` handler (function) which looks
|
|
like this:
|
|
```clj
|
|
#(re-frame.core/dispatch [:delete-item 2486])
|
|
```
|
|
|
|
`dispatch` is the means by which you emit an `event`. An `event` is a vector and, in this case,
|
|
it has 2 elements: `[:delete-item 2486]`. The first element,
|
|
`:delete-item`, is the kind of event. The `rest` is optional, and is whatever else needs to
|
|
be known about the event - in this case, my made-up id, `2486`, of the item to delete.
|
|
|
|
#### Code For Domino 2
|
|
|
|
The `event handler`, `h`, associated with
|
|
`:delete-item` is called to compute the `effect` of this event.
|
|
|
|
This handler function, `h`, must take two arguments: the state-of-the-world
|
|
and the event, and it must return an effects map. Without going into any
|
|
explanations at this early point, here's a sketch of what a handler
|
|
might look like:
|
|
```clj
|
|
(defn h
|
|
[{:keys [db]} event] ;; args: db from coeffect, event
|
|
(let [item-id (second event)] ;; extract id from event vector
|
|
{: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:
|
|
```clj
|
|
(re-frame.core/reg-event-fx :delete-item h)
|
|
```
|
|
|
|
#### Code For Domino 3
|
|
|
|
An `effect handler` (function) actions the `effect`, and
|
|
resets application state to the newly computed value. This is a mutative
|
|
step, facilitated by re-frame, which you won't have to do explicitly.
|
|
|
|
#### Code For Domino 4
|
|
|
|
Because the application state changed, a query (function) over the application
|
|
state is called automatically (reactively), and it computes the list of items (which
|
|
now, because of domino 3, no longer contains the deleted item).
|
|
|
|
Because the items
|
|
are already stored in app state, there's not a lot to compute in this case. This
|
|
subscription acts more like an accessor.
|
|
```clj
|
|
(defn query-fn
|
|
[db _] ;; db is current app state
|
|
(:items db)) ;; not much of materialised view
|
|
```
|
|
|
|
Such a query-fn must be registered, (reasons become obvious in the next domino) like this:
|
|
```clj
|
|
(re-frame.core/reg-sub :query-items query-fn)
|
|
```
|
|
|
|
#### Code For Domino 5
|
|
|
|
Because the query function computed a new value, a view (function) which subscribes
|
|
to that value, is called automatically (reactively) to re-compute DOM. It produces
|
|
a hiccup-formatted data structure describing the DOM nodes required (no DOM nodes
|
|
for the deleted item, obviously, but otherwise the same DOM as last time).
|
|
|
|
```clj
|
|
(defn items-view
|
|
[]
|
|
(let [items (subscribe [:query-items])]
|
|
[div: (map item-render @items])) ;; assume item-render already written
|
|
```
|
|
|
|
Notice how `items` is "sourced". A view function uses `subscribe` and the key
|
|
originally used to register a query function.
|
|
|
|
#### Code For Domino 6
|
|
|
|
The computed DOM (hiccup) is made real by Reagent/React. No code required. Just happens.
|
|
|
|
The DOM "this
|
|
time" is the same as last time, except for the absence of DOM for the
|
|
deleted item.
|
|
|
|
The key point to understand about 3-4-5-6 is that a change to app state, triggers queries to rerun,
|
|
which, in turn, triggers views to rerun which, in turn, causes fresh DOM in the broiwser All reactively. Boom, boom, boom.
|
|
One domino after the other. But efficiently, with short cuituits.
|
|
|
|
#### Aaaaand we're done
|
|
|
|
At this point, the re-frame app returns to a quiescent state,
|
|
waiting for the next event. And, when it happens, another
|
|
6 domino cascade will follow.
|
|
|
|
## 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. Think about that. 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".
|
|
|
|
I cannot stress too much what a big deal this is. It can seem
|
|
like 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 to leverage similar power 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 and proven in the large
|
|
|
|
re-frame was released in early 2015, and has since [been](https://www.fullcontact.com)
|
|
successfully
|
|
[used](https://www.nubank.com.br)
|
|
by
|
|
[quite](http://open.mediaexpress.reuters.com/)
|
|
a
|
|
[few](https://rokt.com/) companies and
|
|
individuals to build complex apps, many running beyond 40K lines of
|
|
ClojureScript.
|
|
|
|
<img align="right" src="/images/scale-changes-everything.jpg?raw=true">
|
|
|
|
**Scale changes everything.** 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 has
|
|
worked out well. Some have been effusive in their praise.
|
|
|
|
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'd
|
|
like with devcards (which is a library vs framework issue) - we're still
|
|
puzzling over some aspects and tweaking as we go. All libraries
|
|
represent a point in the possible design space, with pros and cons.
|
|
|
|
And, yes, re-frame is fast, straight out of the box. And, yes, it has
|
|
a good testing story (unit and behavioural). And, yes, it works in with figwheel to create
|
|
a delightful hot-loading development story. And, yes, it has
|
|
a fun specialist tooling, and a community,
|
|
and useful 3rd party libraries.
|
|
|
|
### Where Do I Go Next?
|
|
|
|
We haven't yet looked at code, but **at this point you
|
|
already know 40% of re-frame.** There's detail to fill in, for sure,
|
|
but the core concepts are now known to you.
|
|
|
|
Next, you need to do the code walk-through in the tutorial. This
|
|
will get your knowledge to about 70%. The
|
|
final 30% always comes incrementally with use and by reading the rest of the
|
|
docs (of which there's a few).
|
|
|
|
So, next, go here: <br>
|
|
https://github.com/Day8/re-frame/blob/master/docs/README.md
|
|
|
|
Experiment with these examples: <br>
|
|
https://github.com/Day8/re-frame/tree/master/examples
|
|
|
|
Use a template to create your own project: <br>
|
|
Client only: https://github.com/Day8/re-frame-template <br>
|
|
Front and back: http://www.luminusweb.net/
|
|
|
|
Use these resources: <br>
|
|
https://github.com/Day8/re-com
|
|
XXX
|
|
|
|
###
|
|
|
|
Good news. If you've read this far,
|
|
your insiders T-shirt will be arriving soon - it
|
|
will feature turtles
|
|
and [xkcd](http://xkcd.com/1416/). We're still working on the hilarious caption bit. Open a
|
|
repo issue with a suggestion.
|
|
|
|
|
|
### 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/
|