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. 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, 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 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, re-frame has pioneered ideas like event handler middleware,
coeffect accretion, and de-duplicated signal graphs. 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, unidirectional data flow, pristinely pure functions,
interceptors, coeffects, conveyor belts, statechart-friendliness (FSM) interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)
and claims an immaculate hammock conception. It also has a charming 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 Computationally, each iteration of the loop involves a
six domino cascade. six domino cascade.
One domino triggers the next, which triggers the next, et cetera, until we are One domino triggers the next, which triggers the next, et cetera, boom, boom, boom, until we are
back at the beginning of the loop, whereupon the dominoes spring to attention back at the beginning of the loop, and the dominoes spring to attention
again, ready for the next iteration of the same cascade. 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 ### 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. "descriptions of required DOM", returned by the view functions of Domino 5, are made real.
The browser DOM nodes are mutated. The browser DOM nodes are mutated.
## Managing mutation
### 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
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.
@ -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 In both cases, you don't need to worry yourself about this dirty work. re-frame looks
after those dominoes. 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"> <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 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. 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 **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. 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 ### Code For Domino 1
The delete button for that 3rd item will have an `on-click` handler (function) which looks The delete button for that 3rd item will look like this:
like this:
```clj ```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, A re-frame `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]` (where `2486` in the made-up id for that 3rd item).
`: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. 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 ### 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]`. compute the `effect` of the event `[:delete-item 2486]`.
Earlier, on program startup, `h` would have been On app startup, `re-frame.core/reg-event-fx` would have been used to
registered for handling `:delete-item` `events` like this: register this `h` as the handler for `:delete-item` events, like this:
```clj ```clj
(re-frame.core/reg-event-fx ;; a part of the re-frame API (re-frame.core/reg-event-fx ;; a part of the re-frame API
:delete-item ;; the kind of event :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: `h` is written to take two arguments:
1. a `coeffects` map which contains the current state of the world (including app state) 1. a `coeffects` map which contains the current state of the world (including app state)
2. the `event` 2. the `event` to handle
`h` returns a map of `effects` - a description It is the job of `h` to compute how the world should be changed by the event, and
of how the world should be changed by the event. it returns a map of `effects` - a description of the those changes.
Here's a sketch (we are at 30,000 feet): Here's a sketch (we are at 30,000 feet):
```clj ```clj
(defn h (defn h
[{:keys [db]} event] ;; args: db from coeffect, event [coeffects event] ;; args: db from coeffect, event
(let [item-id (second event)] ;; extract id from event vector (let [item-id (second event) ;; extract id from event vector
{:db (dissoc-in db [:items item-id])})) ;; effect is change db 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 of the world into that first `coeffects` argument (map). Different
event handlers need to know different things about the world event handlers need to different "things" to get their job done. But
in order to get their job done. But current "application state" current "application state" is one aspect of the world which is
is one aspect of the world which is invariably needed, and it is made invariably needed, and it is made available by default in the `:db` key.
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 ### Code For Domino 3
@ -338,7 +358,7 @@ An `effect handler` (function) actions the `effects` returned by `h`.
Here's what `h` returned: Here's what `h` returned:
```clj ```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 Each key of the map identifies one kind
of `effect`, and the value for that key supplies further details. 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. 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 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: Just to be clear, if `h` had returned:
```clj ```clj
{:wear {:pants "velour flares" :belt false} {:wear {:pants "velour flares" :belt false}
:tweet "Okay, yes, I am Satoshi. #coverblown"} :tweet "Okay, yes, I am Satoshi. #coverblown"}
``` ```
Then the two effects handlers registered for `:wear` and `:tweet` would 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 be called to action those two effects. And, no, re-frame
does not supply standard effect handlers for either, so you would have had does not supply standard effect handlers for either, so you would have had
to have written them yourself (see how in a later tutorial). to have written them yourself (see how in a later tutorial).
### Code For Domino 4 ### Code For Domino 4
Because an effect handler just updated "app state", Because an effect handler just mutated "application state",
a query (function) over this app state is called automatically (reactively), a query (function) over this app state is automatically called (reactively).
itself computing the list of items.
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 Because the items are stored in app state, there's not a lot
to compute in this case. This to compute and, instead, it acts more like an extractor or accessor,
query function acts more like an extractor or accessor: just plucking the list of items out of application state:
```clj ```clj
(defn query-fn (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 (:items db)) ;; not much of a materialised view
``` ```
On program startup, such a `query-fn` must be associated with a `query-id`, 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 ```clj
(re-frame.core/reg-sub ;; part of the re-frame API (re-frame.core/reg-sub ;; part of the re-frame API
:query-items ;; query id :query-items ;; query id
query-fn) ;; query fn 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". use `query-fn` to compute it".
### Code For Domino 5 ### Code For Domino 5
Because the query function for `:query-items` just re-computed a new value, Because the query function for `:query-items` just re-computed a new value,
any view (function) which subscribes to `:query-items` any view (function) which uses a `(subscribe [:query-items])`
is called automatically (reactively) to re-compute DOM. is called automatically (reactively) to re-compute new DOM.
View functions compute a data structure, in hiccup format, describing View functions compute a data structure, in hiccup format, describing
the DOM nodes required. In this case, there will be no DOM nodes the DOM nodes required. In this case, the view functions will *not* be generating
for the now-deleted item, obviously, but otherwise the same DOM as last time. hiccup for the now-deleted item obviously but, other than this,
the hiccup computed will be the same as last time.
```clj ```clj
(defn items-view (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 [:div (map item-render @items)])) ;; assume item-render already written
``` ```
Notice how `items` is "sourced" from "app state" via `subscribe`. Notice how `items` is "sourced" from "app state" via `re-frame.core/subscribe`.
It is called with a query id to identify what data it needs. 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 ### Code For Domino 6
@ -443,7 +484,7 @@ When building a re-frame app, you:
- write Reagent view functions (view layer) (domino 5) - 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 re-frame was released in early 2015, and has since
[been](https://www.fullcontact.com) successfully [been](https://www.fullcontact.com) successfully
@ -474,28 +515,16 @@ and useful 3rd party libraries.
## Where Do I Go Next? ## Where Do I Go Next?
**At this point you **At this point you already know 50% of re-frame.** The full [docs are here](/docs).
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.
Next you need to read the other three articles in the [Introduction section](/docs#introduction): There are two example apps to play with: <br>
* [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>
https://github.com/Day8/re-frame/tree/master/examples https://github.com/Day8/re-frame/tree/master/examples
Use a template to create your own project: <br> Use a template to create your own project: <br>
Client only: https://github.com/Day8/re-frame-template <br> Client only: https://github.com/Day8/re-frame-template <br>
Full Stack: http://www.luminusweb.net/ 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 https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
### T-Shirt Unlocked ### T-Shirt Unlocked

View File

@ -102,17 +102,22 @@ Sincerely, The Self-appointed President of the Cursor Skeptic's Society.
## DSLs and VMs ## 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 Every re-frame app will have a different set of `events`. Your job is
design exactly the right set of them. For your app,`events` will be the "language of the system". to design exactly the right ones for any given app you build. These `events`
They capture intent (generally the user's intent). They provide the eloquence. will be the "language of the system". They'll represent intent
(generally the user's). They'll provide the eloquence.
And they are data. 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 ```cljs
(def events (def collected-events
[ [
[:clear] [:clear]
[:new :triangle 1 2 3] [: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 ```asm
mov eax, ebx mov eax, ebx
sub eax, 216 sub eax, 216
mov BYTE PTR [ebx], 2 mov BYTE PTR [ebx], 2
``` ```
Assembler instructions are data, right? They have to be "executed" by a machine. I'd Assembly instructions are data, right? Data which happens to be "executable"
like you to look back at the collection of events above the same way. by the right machine. An x86 machine in the case above.
Mental Model: I'd like you to now look back at that collection of events in the
- Events are the assembly language of your app. They are instructions. same way - data which can be executed - by the right machine.
- 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.
> 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 ## It does Event Sourcing