This commit is contained in:
Mike Thompson 2016-10-24 14:21:47 +11:00
parent b1b4120ab3
commit 3a659dae94
7 changed files with 111 additions and 146 deletions

View File

@ -87,6 +87,8 @@ D3 (from @zachcp):
- Code: https://github.com/zachcp/simplecomponent
- Example: http://zachcp.github.io/simplecomponent/
A different take on using D3:
https://gadfly.io/2016-10-22-d3-in-reagent.html
### Advanced Lifecycle Methods

View File

@ -1,8 +1,8 @@
## Code Walk-through
At this point in your reading, you are armed with:
- a high level understanding of the 5 domino process (from the repo's README)
- an understanding of application state (the previous Tutorial)
- a high level understanding of the 5 domino process (from re-frame's README)
- an understanding of application state (from the previous tutorial)
In this tutorial, **we'll look at re-frame code**.
@ -348,7 +348,7 @@ events.
(defn clock
[]
[:div.example-clock
{:style {:color (rf/listen [:time-color])}}
{:style {:color @(rf/subscribe [:time-color])}}
(-> (rf/listen [:time])
.toTimeString
(clojure.string/split " ")
@ -359,7 +359,7 @@ events.
[:div.color-input
"Time color: "
[:input {:type "text"
:value (rf/listen [:time-color])
:value @(rf/subscribe [:time-color])
:on-change #(rf/dispatch [:time-color-change (-> % .-target .-value)])}]]) ;; <---
(defn ui
@ -370,7 +370,20 @@ events.
[color-input]])
```
Naming: sub-val ???
Naming: sub-val ??? sub-r
### Components Like Templates?
A `component` such as `greet` is like the templates you'd find in
Django, Rails, Handlebars or Mustache -- it maps data to HTML -- except for two massive differences:
1. you have the full power of ClojureScript available to you (generating a Clojure data structure). The
downside is that these are not "designer friendly" HTML templates.
2. these templates are reactive. When their input Signals change, they
are automatically rerun, producing new DOM. Reagent adroitly shields you from the details, but
the renderer of any `component` is wrapped by a `reaction`. If any of the the "inputs"
to that render change, the render is rerun.
## Kick Starting The App

View File

@ -7,7 +7,7 @@ possible.
But there's other interesting perspectives on re-frame
which will considerably deepen your understanding of its design,
and how to get the best from it.
and how to get the best from it.
This tutorial is a tour
of these ideas, justifications and insights. It is a little rambling, but I
@ -168,33 +168,86 @@ Elm Architecture.
## It does FSM
And this perspective can be useful ...
> Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation
> of a hierarchical FSM <br>
> -- my eleventh rule (see [Greenspun's tenth rule](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule))
> -- [my eleventh rule](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule)
Previously, I commented that `event handlers` (domino 2) collectively
represent the "control layer" of the application. They contain logic
which interprets arriving events in the context of existing state,
`event handlers` collectively
implement the "control" part of an application. Their logic
interprets arriving events in the context of existing state,
and they "step" the application state "forward".
In this way, `events` act like the `triggers` in a finite state machine, and
the event handlers act like the rules which govern how a the state machine
`events` act a bit like the `triggers` in a finite state machine, and
the event handlers act like the rules which govern how the state machine
moves from one logical state to the next.
The "logical state" will be a function over the values in `app-db`. In the simplest
case `app-db` will contain a single value which represents logical state.
Not every app has lots of logical states, but some do, and if you are implementing
one of them, then formally recognising it and using a technique like
[State Charts](https://www.amazon.com/Constructing-User-Interface-Statecharts-Horrocks/dp/0201342782)
will help greatly in getting a clean design and a nice data model.
will help greatly in getting a clean design and fewer bugs.
Perspective:
The beauty of re-frame from a FSM point of view is that all the state is
in one place - unlike OO systems where the state is distributed (and synchronized)
across many objects. So implementing your control logic as a FSM is
both possible and natural in re-frame, whereas it is often difficult and
contrived to do so in other kinds of architecture (in my experience).
So, members of the jury, I put it to you that:
- the first 3 dominoes implement an [Event-driven finite-state machine](https://en.wikipedia.org/wiki/Event-driven_finite-state_machine)
- the last 2 dominoes reactively render the current state of this FSM for the user to observe
- the last 2 dominoes render the current state of this FSM for the user to observe
Depending on your app, this may or may not be a useful mental model,
but one thing for sure ...
Events - that's the way we roll.
## Data Oriented Design
In the readme ... XXX
Events are data - `[:delete-item 42]`
That's almost like a function call `(delete-item 42)`. Kinda. So why prefer data?
Using data gives us:
- late binding
- logability and event sourcing
- a more flexible version of "partial" (curring)
## Derived Data
There's a video I'd like you to watch from
[StrangeLoop](https://www.youtube.com/watch?v=fU9hR3kiOK0) (40 mins, sorry).
XXX
If you have then, given the explanation above, you might twig to the idea that `app-db` is
really a derived value (of the `perpetual reduce`).
And yet, it acts as the authoritative source of state in the app. And yet, it isn't, it is simply
a piece of derived state. And
yet, it is the source.
Hmm. This is an infinite loop of sorts. **Derived data is flowing around the
loop, reactively, through pure functions.** There is a pause in the loop whenever we wait
for a new event, but the moment we get it, it's another iteration of the "derived data" FRP loop.
Derived values, all the way down, forever.
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.
## Prefer Dumb Views - Part 1
Many events are dispatched by the DOM in response to user actions.

View File

@ -31,7 +31,7 @@ Perhaps:
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 coming up shortly.
4. You like a bit of social proof!! I can report that
4. You like social proof!!
re-frame is impressively buzzword compliant: it has reactivity,
unidirectional data flow, pristinely pure functions,
interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)

View File

@ -11,6 +11,17 @@ introductory Reagent tutorials before going on. Try:
- [this one](https://github.com/jonase/reagent-tutorial) or
- [Building Single Page Apps with Reagent](http://yogthos.net/posts/2014-07-15-Building-Single-Page-Apps-with-Reagent.html).
## Implements Reactive Data Flows
This document describes how re-frame implements
the reactive data flows in dominoes 4 and 5 (queries and views).
It explains
the low level mechanics of the process which not something you
need to know initially. So, you can defer reading and understanding
this until later, if you wish. But you should at some point circle
back and grok it. It isn't hard at all.
@ -35,6 +46,8 @@ your neck, read it again until it does, because it is important.
Steve Grand
## Reactive Programming
We'll get to the meat in a second, I promise, but first one final, useful diversion ...
@ -252,17 +265,6 @@ ratom-nature, so we'll happily continue believing it is a `ratom` and no harm wi
On with the rest of my lies and distortions...
### Components Like Templates?
A `component` such as `greet` is like the templates you'd find in
Django, Rails, Handlebars or Mustache -- it maps data to HTML -- except for two massive differences:
1. you have the full power of ClojureScript available to you (generating a Clojure data structure). The
downside is that these are not "designer friendly" HTML templates.
2. these templates are reactive. When their input Signals change, they
are automatically rerun, producing new DOM. Reagent adroitly shields you from the details, but
the renderer of any `component` is wrapped by a `reaction`. If any of the the "inputs"
to that render change, the render is rerun.
### React etc.
@ -553,117 +555,9 @@ Summary:
even for large, deep nested data structures.
## Event Flow
Events are what flow in the opposite direction.
In response to user interaction, a DOM will generate
events like "clicked delete button on item 42" or
"unticked the checkbox for 'send me spam'".
These events have to be "handled". The code doing this handling might
mutate app state (in `app-db`), or request more data from the server, or POST somewhere and wait for a response, etc.
In fact, all these actions ultimately result in changes to the `app-db`.
An application has many handlers, and collectively
they represent the **control layer of the application**.
In re-frame, the backwards data flow of events happens via a conveyor belt:
```
app-db --> components --> Hiccup --> Reagent --> VDOM --> React --> DOM
^ |
| v
handlers <------------------- events -----------------------------------------
a "conveyor belt" takes events
from the DOM to the handlers
```
Generally, when the user manipulates the GUI, the state of the application changes. In our case,
that means the `app-db` will change. After all, it **is** the state. And the DOM presented to
the user is a function of that state.
So that tends to be the cycle:
1. the user clicks something which causes an event to be dispatched
2. a handler manages the event
3. and causes `app-db` to change (mutation happens here!)
4. which then causes a re-render
5. the user sees something different
6. goto #1
That's our water cycle.
Because handlers are that part of the system which does `app-db` mutation, you
could almost imagine them as a "stored procedures" on a
database. Almost. Stretching it? We do like our in-memory
database analogies.
### Control Via FSM
Above, I commented that event handlers collectively represent the "control layer" of the
application. They contain
logic which interprets arriving events and they "step" the application "forward"
via mutations to `app-db`.
Our `delete-handler` above is trivial, but as an application grows more features, the logic in many
handlers will become more complicated, and they will have to query BOTH the current state of the app
AND the arriving event vector to determine what action to take.
If the app is in logical State A, and event X arrives, then the handler will move the app to logical state B
(by changing values in `app-db`).
Sound like anything you learned in those [Theory Of Computation](https://www.youtube.com/watch?v=Pt6GBVIifZA)
lectures?
That's right - as an app becomes more complex, the handlers are likely to be collectively implementing a
[Finite State Machine](http://en.wikipedia.org/wiki/Finite-state_machine):
- your app is in a certain logical state (defined by the current values in `app-db`)
- the arriving event vector represents a `trigger`.
- the event handler implements "a transition", subject to BOTH the current logical state and the arriving trigger.
- after the handler has run, the transition may have moved the app into a new logical state.
- Repeat.
Not every app has lots of logical `states`, but many do, and if you are implementing one of them, then formally
recognising it and using a technique like
[state charts](http://www.amazon.com/Constructing-User-Interface-Statecharts-Horrocks/dp/0201342782) will help
greatly in getting a clean design and a nice datamodel.
The beauty of re-frame from a FSM point of view is that all the data is in one place - unlike OO systems where
the data is distributed (and synchronized) across many objects. So implementing your control logic as a FSM is
both possible and natural in re-frame, whereas it is often difficult and contrived to do so in other
kinds of architecture (in my experience).
### Derived Data, Everywhere, flowing
Have you watched that
[StrangeLoop presentation ](https://www.youtube.com/watch?v=fU9hR3kiOK0) yet?
I hope so. Database as a stream, right?
If you have then, given the explanation above, you might twig to the idea that `app-db` is
really a derived value (of the `perpetual reduce`).
And yet, it acts as the authoritative source of state in the app. And yet, it isn't, it is simply
a piece of derived state. And
yet, it is the source.
Hmm. This is an infinite loop of sorts. **Derived data is flowing around the
loop, reactively, through pure functions.** There is a pause in the loop whenever we wait
for a new event, but the moment we get it, it's another iteration of the "derived data" FRP loop.
Derived values, all the way down, forever.
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.
Back to the more pragmatic world ...

View File

@ -1,4 +1,4 @@
(defproject simple-re-frame "0.8.0"
(defproject simple "0.8.0"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.227"]
[reagent "0.6.0-rc"]

View File

@ -9,7 +9,9 @@
(let [now (js/Date.)]
(rf/dispatch [:timer now]))) ;; <-- dispatch used
;; call the dispatching function every second
;; Call the dispatching function every second.
;; `defonce` is like `def` but it ensures only instance is ever
;; created in the face of figwheel hot-reloading of this file.
(defonce do-timer (js/setInterval dispatch-timer-event 1000))
@ -37,9 +39,13 @@
;; -- Domino 4 - Query -------------------------------------------------------
(rf/reg-sub
:time
:time-str
(fn [db _] ;; db is current app state. 2nd usused param is query vector
(:time db))) ;; return a query computation over the application state
(-> db
:time
.toTimeString
(clojure.string/split " ")
first)))
(rf/reg-sub
:time-color
@ -53,10 +59,7 @@
[]
[:div.example-clock
{:style {:color (rf/listen [:time-color])}}
(-> (rf/listen [:time])
.toTimeString
(clojure.string/split " ")
first)])
(rf/listen [:time-str])]) ;; XXX listen
(defn color-input
[]