Dos updates
This commit is contained in:
parent
131e0ba30c
commit
b6c301b31c
183
README.md
183
README.md
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue