Dos updates

This commit is contained in:
Mike Thompson 2017-07-15 14:35:28 +10:00
parent 131e0ba30c
commit b6c301b31c
2 changed files with 132 additions and 93 deletions

183
README.md
View File

@ -46,10 +46,10 @@ Perhaps:
In this space, re-frame is very old, hopefully in a Gandalf kind of way.
First designed in Dec 2014, it even slightly pre-dates the official Elm Architecture,
although thankfully we were influenced by early-Elm concepts like `foldp` and `lift`, as well as
terrific Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then,
Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then,
re-frame has pioneered ideas like event handler middleware,
coeffect accretion, and de-duplicated signal graphs.
5. Which leads us to the most important point: **re-frame is impressively buzzword compliant**. It has reactivity,
5. Which brings us to the most important point: **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
@ -129,11 +129,17 @@ you to understand re-frame, is **practically proof** it does physics.
Computationally, each iteration of the loop involves a
six domino cascade.
One domino triggers the next, which triggers the next, et cetera, until we are
back at the beginning of the loop, whereupon the dominoes spring to attention
One domino triggers the next, which triggers the next, et cetera, boom, boom, boom, until we are
back at the beginning of the loop, and the dominoes spring to attention
again, ready for the next iteration of the same cascade.
The six dominoes are ...
The six dominoes are:
1. Event dispatch
2. Event handling
3. Effect handling
4. Query
5. View
6. DOM
### 1st Domino - Event Dispatch
@ -243,19 +249,7 @@ This is the step in which the hiccup-formatted
"descriptions of required DOM", returned by the view functions of Domino 5, are made real.
The browser DOM nodes are mutated.
### A Cascade Of Simple Functions
**Each of the dominoes you write are simple, pure functions** which
can be described, understood and
tested independently. They take data, transform it and return new data.
The loop itself is very mechanical in operation.
So, there's a regularity, simplicity and
certainty to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging.
### Managing mutation
## Managing mutation
The two sub-cascades 1-2-3 and 4-5-6 have a similar structure.
@ -266,12 +260,22 @@ the last domino which does the dirty work and realises these descriptions.
In both cases, you don't need to worry yourself about this dirty work. re-frame looks
after those dominoes.
## Code Fragments For The Dominos
### A Cascade Of Simple Functions
**You'll (mostly) be writing pure functions** which
can be described, understood and
tested independently. They take data, transform it and return new data.
The loop itself is mechanical and predictable in operation.
So, there's a regularity to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging.
## The Dominoes Again - With Code Fragments
<img align="right" src="/images/Readme/todolist.png?raw=true">
So that was the view of re-frame from 60,000 feet. We'll now shift to 30,000 feet
and look again at each domino, but this time with code fragments.
So that was the view of re-frame from 60,000 feet. We'll now shift down to 30,000 feet
and look again at each domino, but this time with code fragments.
**Imagine:** we're working on a SPA which displays a list of items. You have
just clicked the "delete" button next to the 3rd item in the list.
@ -284,26 +288,33 @@ to completely grok the terse code presented below. We're still at 30,000 feet. D
### Code For Domino 1
The delete button for that 3rd item will have an `on-click` handler (function) which looks
like this:
The delete button for that 3rd item will look like this:
```clj
#(re-frame.core/dispatch [:delete-item 2486])
(defn delete-button
[item-id]
[:div.garbage-bin
:on-click #(re-frame.core/dispatch [:delete-item item-id])])
```
`dispatch` emits an `event`.
That the `on-click` handler uses `re-frame.core/dispatch` to emit an `event`.
A re-frame `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, further data about the
`event` - in this case, my made-up id, `2486`, for the item to delete.
it has 2 elements: `[:delete-item 2486]` (where `2486` in the made-up id for that 3rd item).
The first element of an event vector,
`:delete-item`, is the kind of event. The rest is optional, useful data about the
`event`.
Events express intent in a domain specific (app specific) way.
They are the language of your re-frame system.
### Code For Domino 2
An `event handler` (function), `h`, is called to
An `event handler` (function), called say `h`, is called to
compute the `effect` of the event `[:delete-item 2486]`.
Earlier, on program startup, `h` would have been
registered for handling `:delete-item` `events` like this:
On app startup, `re-frame.core/reg-event-fx` would have been used to
register this `h` as the handler for `:delete-item` events, like this:
```clj
(re-frame.core/reg-event-fx ;; a part of the re-frame API
:delete-item ;; the kind of event
@ -312,25 +323,34 @@ registered for handling `:delete-item` `events` like this:
`h` is written to take two arguments:
1. a `coeffects` map which contains the current state of the world (including app state)
2. the `event`
`h` returns a map of `effects` - a description
of how the world should be changed by the event.
2. the `event` to handle
It is the job of `h` to compute how the world should be changed by the event, and
it returns a map of `effects` - a description of the those changes.
Here's a sketch (we are at 30,000 feet):
```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
[coeffects event] ;; args: db from coeffect, event
(let [item-id (second event) ;; extract id from event vector
db (:db coeffects) ;; extract the current application state
{:db (dissoc-in db [:items item-id])})) ;; effect is change app state
```
re-frame has ways (beyond us here) to inject necessary aspects
re-frame has ways (described in later tutorials) to inject necessary aspects
of the world into that first `coeffects` argument (map). Different
event handlers need to know different things about the world
in order to get their job done. But current "application state"
is one aspect of the world which is invariably needed, and it is made
available by default in the `:db` key.
event handlers need to different "things" to get their job done. But
current "application state" is one aspect of the world which is
invariably needed, and it is made available by default in the `:db` key.
BTW, here more idiomatic rewrite of `h` which uses "destructuring":
```clj
(defn h
[{:keys [db]} [_ item-id]] ;; <--- new: obtain db and id directly
{:db (dissoc-in db [:items item-id])}) ;; same as before
```
### Code For Domino 3
@ -338,7 +358,7 @@ An `effect handler` (function) actions the `effects` returned by `h`.
Here's what `h` returned:
```clj
{:db (dissoc-in db [:items 2486])}
{:db (dissoc-in db [:items 2486])} ;; db is a map of some structure
```
Each key of the map identifies one kind
of `effect`, and the value for that key supplies further details.
@ -350,52 +370,61 @@ This update of "app state" is a mutative step, facilitated by re-frame
which has a built-in `effects handler` for the `:db` effect.
Why the name `:db`? Well, re-frame sees "app state" as something of an in-memory
database. More on that soon.
database. More on this is a following tutorial.
Just to be clear, if `h` had returned:
```clj
{:wear {:pants "velour flares" :belt false}
:tweet "Okay, yes, I am Satoshi. #coverblown"}
```
Then the two effects handlers registered for `:wear` and `:tweet` would
be called in this domino to action those two effects. And, no, re-frame
Then, the two effects handlers registered for `:wear` and `:tweet` would
be called to action those two effects. And, no, re-frame
does not supply standard effect handlers for either, so you would have had
to have written them yourself (see how in a later tutorial).
### Code For Domino 4
Because an effect handler just updated "app state",
a query (function) over this app state is called automatically (reactively),
itself computing the list of items.
Because an effect handler just mutated "application state",
a query (function) over this app state is automatically called (reactively).
This query (function) computes "a materialised view" of the
application state - a version of the application state which is useful to
the next domino, 5.
Remember, we are in `v = f(s)`, and this domino is about delivering the right
data to later functions (domino 5) which compute DOM.
Now, in this particular case, the query function is pretty trivial.
Because the items are stored in app state, there's not a lot
to compute in this case. This
query function acts more like an extractor or accessor:
to compute and, instead, it acts more like an extractor or accessor,
just plucking the list of items out of application state:
```clj
(defn query-fn
[db _] ;; db is current app state
[db v] ;; db is current app state, v the query vector
(:items db)) ;; not much of a materialised view
```
On program startup, such a `query-fn` must be associated with a `query-id`,
(for reasons obvious in the next domino) like this:
(so it can be used via `subscribe` in the next domino) using `re-frame.core/reg-sub`,
like this:
```clj
(re-frame.core/reg-sub ;; part of the re-frame API
:query-items ;; query id
query-fn) ;; query fn
```
Which says "if you see a query (subscribe) for `:query-items`,
Which says "if you see a `(subscribe [:query-items])`, then
use `query-fn` to compute it".
### Code For Domino 5
Because the query function for `:query-items` just re-computed a new value,
any view (function) which subscribes to `:query-items`
is called automatically (reactively) to re-compute DOM.
any view (function) which uses a `(subscribe [:query-items])`
is called automatically (reactively) to re-compute new DOM.
View functions compute a data structure, in hiccup format, describing
the DOM nodes required. In this case, there will be no DOM nodes
for the now-deleted item, obviously, but otherwise the same DOM as last time.
the DOM nodes required. In this case, the view functions will *not* be generating
hiccup for the now-deleted item obviously but, other than this,
the hiccup computed will be the same as last time.
```clj
(defn items-view
@ -404,8 +433,20 @@ for the now-deleted item, obviously, but otherwise the same DOM as last time.
[:div (map item-render @items)])) ;; assume item-render already written
```
Notice how `items` is "sourced" from "app state" via `subscribe`.
It is called with a query id to identify what data it needs.
Notice how `items` is "sourced" from "app state" via `re-frame.core/subscribe`.
It is called with a vector argument, and the first element of that vector is
a query-id which identifies the "materialised view" required.
Note: `subscribe` queries can be parameterised. So, in real world apps
you might have this:<br>
`(subscribe [:items "blue"])`
The vector identifies, first, the query, and then
supplies further arguments. You could think of that as representing `select * from Items where colour="blue"`.
Except there's no SQL available and you would be the one to implement
the more sophisticated `query-fn` to handle those
further arguments. More in later tutorials.
### Code For Domino 6
@ -443,7 +484,7 @@ When building a re-frame app, you:
- write Reagent view functions (view layer) (domino 5)
## It is mature and proven in the large
## re-frame is mature and proven in the large
re-frame was released in early 2015, and has since
[been](https://www.fullcontact.com) successfully
@ -474,28 +515,16 @@ and useful 3rd party libraries.
## Where Do I Go Next?
**At this point you
already know 50% of re-frame.** There's detail to fill in, for sure,
but the core concepts, and even basic coding techniques, are now known to you.
**At this point you already know 50% of re-frame.** The full [docs are here](/docs).
Next you need to read the other three articles in the [Introduction section](/docs#introduction):
* [Application State](/docs/ApplicationState.md)
* [Code Walkthrough](/docs/CodeWalkthrough.md)
* [Mental Model Omnibus](/docs/MentalModelOmnibus.md)
This will push your knowledge to about 70%. The
final 30% will come incrementally with use, and by reading the other
tutorials (of which there are a few).
You can also experiment with these two examples: <br>
There are two example apps to play with: <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>
Full Stack: http://www.luminusweb.net/
Use these resources: <br>
And please be sure to review these further resources: <br>
https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
### T-Shirt Unlocked

View File

@ -102,17 +102,22 @@ Sincerely, The Self-appointed President of the Cursor Skeptic's Society.
## DSLs and VMs
`Events` are central to re-frame's architecture. Nothing happens without a dispatched event.
`Events` are cardinal to re-frame - they are a fundamental organising principle.
Every app will have a different `events`. Indeed, part of your job will be to
design exactly the right set of them. For your app,`events` will be the "language of the system".
They capture intent (generally the user's intent). They provide the eloquence.
Every re-frame app will have a different set of `events`. Your job is
to design exactly the right ones for any given app you build. These `events`
will be the "language of the system". They'll represent intent
(generally the user's). They'll provide the eloquence.
And they are data.
Here's a collection of events from a drawing app:
Imagine we created a drawing application. And then we allowed
someone to use our application, and captured, into a collection,
the events caused by that user's actions (button clicks, drags, etc).
The collection of events might look like this:
```cljs
(def events
(def collected-events
[
[:clear]
[:new :triangle 1 2 3]
@ -122,25 +127,30 @@ Here's a collection of events from a drawing app:
])
```
I'd like you to look upon that collection as you would the following assembler:
Now, consider the following assembly instructions:
```asm
mov eax, ebx
sub eax, 216
mov BYTE PTR [ebx], 2
```
Assembler instructions are data, right? They have to be "executed" by a machine. I'd
like you to look back at the collection of events above the same way.
Assembly instructions are data, right? Data which happens to be "executable"
by the right machine. An x86 machine in the case above.
Mental Model:
- Events are the assembly language of your app. They are instructions.
- These instructions are data. One after another gets executed by your functioning app.
- Collectively, the events you design form a Domain Specific Language (DSL). The language of your system.
- And, collectively, the Event Handlers you register create the Virtual Machine (VM) on which this DSL executes.
I'd like you to now look back at that collection of events in the
same way - data which can be executed - by the right machine.
> So, data is executed by the re-frame VM you create. Which is all data oriented and Clojurian.
Wait. What machine? Well, the Event Handlers you register collectively implement
the "machine" on which these Instructions execute.
I find James Reeves' talks to be excellent (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A)
Summary:
- Events are the assembly language of your app.
- These instructions collectively form a Domain Specific Language (DSL). The language of your system.
- These instructions are data.
- One instruction after another gets executed by your functioning app.
- The Event Handlers you register collectively implement the "machine" on which this DSL executes.
On the subject of DSLs, I'd recommend James Reeves' talk (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A)
## It does Event Sourcing