Docs WIP
This commit is contained in:
parent
b1b4120ab3
commit
3a659dae94
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
@ -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 ...
|
||||
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
Loading…
Reference in New Issue