Docs massaging

This commit is contained in:
Mike Thompson 2017-04-15 12:19:24 +10:00
parent c2492494a8
commit e59168e441
4 changed files with 82 additions and 63 deletions

View File

@ -2,33 +2,35 @@
## Simpler Apps
To build a re-frame app, you:
- design your app's data structure (data layer)
- write and register subscription functions (query layer)
- write Reagent component functions (view layer)
- write and register event handler functions (control layer and/or state transition layer)
- design your app's data structures (data layer)
- write Reagent view functions (domino 5)
- write event handler functions (control layer and/or state transition layer, domino 2)
- write subscription functions (query layer, domino 4)
For simpler apps, you should put code for each layer into separate files:
```
src
├── core.cljs <--- entry point, plus history, routing, etc
├── db.cljs <--- schema, validation, etc (data layer)
├── subs.cljs <--- subscription handlers (query layer)
├── views.cljs <--- reagent components (view layer)
└── events.cljs <--- event handlers (control/update layer)
├── views.cljs <--- reagent views (view layer)
├── events.cljs <--- event handlers (control/update layer)
└── subs.cljs <--- subscription handlers (query layer)
```
For a living example of this approach, look at the [todomvc example](https://github.com/Day8/re-frame/tree/master/examples/todomvc).
*No really, you should absolutely look at the [todomvc example](https://github.com/Day8/re-frame/tree/master/examples/todomvc) example, as soon as possible. It contains all sorts of tips.*
### There's A Small Gotcha
If you adopt this structure there's a gotcha.
If you adopt this structure, there's a gotcha.
`events.cljs` and `subs.cljs` will never be `required` by any other
namespaces. To the Google Closure dependency mechanism it appears as
if these two namespaces are not needed and it doesn't load them.
And, if the code does not get loaded, the registrations in these namespaces
never happen. You'll then be baffled as to why none of your events handlers
never happen. You'll then you'll be puzzled as to why none of your events handlers
are registered.
Once you twig to what's going on, the solution is easy. You must
@ -38,7 +40,7 @@ as that loading happens.
## Larger Apps
Assuming your larger apps has multiple "panels" (or "views") which are
Assuming your larger apps have multiple "panels" (or "views") which are
relatively independent, you might use this structure:
```
src
@ -57,8 +59,6 @@ src
└── panel-n
```
Continue to [Navigation](Navigation.md) to learn how to switch between panels of a larger app.
***
Previous: [Correcting a wrong](SubscriptionsCleanup.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -2,11 +2,11 @@
## Initial Code Walk-through
At this point, you are about 50% of the way to understanding re-frame. You have:
At this point, you are about 55% of the way to understanding re-frame. You have:
- an overview of the 6 domino process [from this repo's README](../README.md)
- an understanding of app state ([from the previous tutorial](ApplicationState.md))
In this tutorial, **we look at re-frame code**. By the end of it, you'll be at 70% knowledge, and ready to start coding.
In this tutorial, **we look at re-frame code**. By the end of it, you'll be at 70% knowledge, and ready to start coding an app.
## What Code?
@ -32,15 +32,15 @@ Then:
3. `lein do clean, figwheel`
4. wait a minute and then open `http://localhost:3449/example.html`
So, what's just happened? The ClojureScript code under `src` has been compiled across to `javascript` and
put into `/resources/public/js/client.js` which is loaded into `/resources/public/example.html` (the HTML you just openned)
So, what's just happened? The ClojureScript code under `/src` has been compiled into `javascript` and
put into `/resources/public/js/client.js` which is loaded into `/resources/public/example.html` (the HTML you just opened)
Figwheel provides for hot-loading, so you can edit the source and watch the loaded HTML change.
Figwheel provides for hot-loading, so you can edit the source code (under `/src`)and watch the loaded HTML change.
## Namespace
Because this example is tiny, the code is in a single namespace which you can find here:
Because this example is tiny, the source code is in a single namespace:
https://github.com/Day8/re-frame/blob/master/examples/simple/src/simple/core.cljs
Within this namespace, we'll need access to both `reagent` and `re-frame`.
@ -53,7 +53,7 @@ So, at the top, we start like this:
## Data Schema
Now, normally, I'd strongly recommended you write a quality schema
Now, normally, I'd strongly recommended that you write a quality schema
for your application state (the data stored in `app-db`). But,
here, to minimise cognitive load, we'll cut that corner.
@ -66,7 +66,7 @@ a two-key map like this:
```
re-frame itself owns/manages `app-db` (see FAQ #1), and it will
supply the value within it (a two-key map in this case)
supply the value within it (the two-key map described above)
to your various handlers as required.
## Events (domino 1)
@ -128,12 +128,12 @@ The consumer of the queue is a `router` which looks after the event's processing
The `router`:
1. inspects the 1st element of an event vector
2. looks in a registry for the event handler which is **registered**
2. looks for the event handler (function) which is **registered**
for this kind of event
3. calls that event handler with the necessary arguments
As a re-frame app developer, your job, then, is to write and register a handler
for each kind of event.
As a re-frame app developer, your job, then, is to write and register an
event handler (function) for each kind of event.
## Event Handlers (domino 2)
@ -148,17 +148,22 @@ In this application, 3 kinds of event are dispatched:
### Two ways to register
Handler functions take `coeffects` (input args) and return `effects`.
Event handler functions take two arguments `coeffects` and `event`,
and they return `effects`.
Handlers can be registered via either `reg-event-fx`
Conceptually, you can think of `coeffects` as being "the current state of the world".
And you can think of event handlers has computing and returning changes (effects) based on
"the current state of the world" and the arriving event.
Event handlers can be registered via either `reg-event-fx`
or `reg-event-db` (`-fx` vs `-db`):
- `reg-event-fx` can take multiple `coeffects` and can return multiple `effects`, while
- `reg-event-db` allows you to write simpler handlers in the common case where you want
- `reg-event-db` allows you to write simpler handlers for the common case where you want
them to take only one `coeffect` - the current app state - and return one `effect` - the
updated app state.
Because of its simplicity, we'll be using the latter here.
Because of its simplicity, we'll be using the latter here: `reg-event-db`.
### reg-event-db
@ -171,13 +176,13 @@ We register event handlers using re-frame's `reg-event-db`:
```
The handler function you provide should expect two arguments:
- `db`, the current application state (the value contained in `app-db`)
- `v`, the event vector
- `v`, the event vector (what was given to `dispatch`)
So, your function will have a signature like this: `(fn [db v] ...)`.
So, your function will have a signature like this: `(fn [db v] ...)`.
Each event handler must compute and return the new state of
Each event handler must compute and return the new state of
the application, which means it returns a
modified version of `db` (or an unmodified one, if there are to be no changes to the state).
modified version of `db` (or an unmodified one, if there are to be no changes to the state).
### :initialize
@ -265,14 +270,14 @@ and run a query over it, returning something called
a "materialised view" of that application state.
When the application state changes, subscription functions are
re-run by re-frame, to compute new values (new materialised views).
re-run by re-frame, to compute new values (new materialised views).
Ultimately, the data returned by `query` functions is used
in the `view` functions (Domino 5).
in the `view` functions (Domino 5).
One subscription can
source data from other subscriptions. So it is possible to
create a tree of dependencies.
create a tree of dependencies.
The Views (Domino 5) are the leaves of this tree. The tree's
root is `app-db` and the intermediate nodes between the two
@ -291,7 +296,7 @@ of subscriptions, and more explanation, can be found in the todomvc example.
:some-query-id ;; query id (used later in subscribe)
a-query-fn) ;; the function which will compute the query
```
Then later, a view function subscribes to a query like this:
Then later, a view function (domino 5) subscribes to a query like this:
`(subscribe [:some-query-id])`, and `a-query-fn` will be used
to perform the query over the application state.
@ -317,13 +322,16 @@ Here's the code:
(:time-color db)))
```
Like I said, both of these queries are trivial. See [todomvc.subs.clj](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs) for more interesting ones.
Like I said, both of these queries are trivial.
See [todomvc.subs.clj](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs)
for more interesting ones.
## View Functions (domino 5)
`view` functions turn data into DOM. They are "State in, Hiccup out" and they are Reagent components.
`view` functions turn data into DOM. They are "State in, Hiccup out" and they are Reagent
components.
Any SPA will have lots of `view` functions, and collectively,
An SPA will have lots of `view` functions, and collectively,
they render the app's entire UI.
### Hiccup
@ -402,11 +410,14 @@ And this view function renders the input field:
:on-change #(rf/dispatch [:time-color-change (-> % .-target .-value)])}]]) ;; <---
```
Notice how it does BOTH a `subscribe` to obtain the current value AND a `dispatch` to say when it has changed.
Notice how it does BOTH a `subscribe` to obtain the current value AND
a `dispatch` to say when it has changed.
It is very common for view functions to run event-dispatching functions. The user's interaction with the UI is usually the largest source of events.
It is very common for view functions to run event-dispatching functions.
The user's interaction with the UI is usually the largest source of events.
And then a `view` function to bring the others together, which contains no subscriptions or dispatching of its own:
And then a `view` function to bring the others together, which contains no
subscriptions or dispatching of its own:
```clj
(defn ui
[]
@ -416,11 +427,13 @@ And then a `view` function to bring the others together, which contains no subsc
[color-input]])
```
Note: `view` functions tend to be organized into a hierarchy, often with data flowing from parent to child via
parameters. So, not every view function needs a subscription. Very often the values passed in from a parent component
are sufficient.
Note: `view` functions tend to be organized into a hierarchy, often with
data flowing from parent to child via
parameters. So, not every view function needs a subscription. Very often
the values passed in from a parent component are sufficient.
Note: `view` functions should never directly access `app-db`. Data is only ever sourced via subscriptions.
Note: `view` functions should never directly access `app-db`. Data is
only ever sourced via subscriptions.
### Components Like Templates?
@ -440,7 +453,8 @@ Below, `run` is the called to kick off the application once the HTML page has lo
It has two tasks:
1. Load the initial application state
2. Load the GUI by "mounting" the root-level function in the hierarchy of `view` functions -- in our case, `ui` --
2. Load the GUI by "mounting" the root-level function in the hierarchy
of `view` functions -- in our case, `ui` --
onto an existing DOM element.
```clj
@ -455,7 +469,8 @@ After `run` is called, the app passively waits for `events`.
Nothing happens without an `event`.
When it comes to establishing initial application state, you'll
notice the use of `dispatch-sync`, rather than `dispatch`. This is a cheat which ensures that a correct
notice the use of `dispatch-sync`, rather than `dispatch`. This is a simplifying cheat
which ensures that a correct
structure exists in `app-db` before any subscriptions or event handlers run.
## Summary
@ -472,14 +487,17 @@ structure exists in `app-db` before any subscriptions or event handlers run.
## Next Steps
You should now take time to carefully review the [todomvc example application](https://github.com/Day8/re-frame/tree/develop/examples/todomvc).
You should now take time to carefully review the
[todomvc example application](https://github.com/Day8/re-frame/tree/develop/examples/todomvc).
On the one hand, it contains a lot of very helpful practical advice. On the other, it does
use some more advanced features like `interceptors` which are covered later in the docs.
After that, you'll be ready to write your own code. Perhaps you will 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/
Obviously you should also go on to read the further documentation.
Obviously, you should also go on to read the further documentation.
***

View File

@ -87,24 +87,25 @@ right value. Nice! But how do we make this magic happen?
### Abracadabra
Each time an event handler is executed, a brand new `context`
is created, and within that `context` is a brand new `:coeffects`
map, which is initially totally empty.
Each time an event handler is executed, a brand new `context` (map)
is created, and within that `context` is a `:coeffects` key which
is a further map (initially empty).
That pristine `context` value (containing a pristine `:coeffects` map) is threaded
through a chain of Interceptors before it finally reaches our event handler,
sitting on the end of a chain, itself wrapped up in an interceptor. We know
which sits on the end of the chain, itself wrapped up in an interceptor. We know
this story well from a previous tutorial.
So, all members of the Interceptor chain have the opportunity to add to `:coeffects`
via their `:before` function. This is where `:coeffects` magic happens. This is how
new keys can be added to `:coeffects`, so that later our event handler magically finds the
right data (like `:local-store`) in its `cofx` argument. It is the Interceptors.
So, all members of the Interceptor chain have the opportunity to `assoc` into `:coeffects`
within their `:before` function, cumulatively building up what it holds. Later, our event handler,
which sits on the end of the chain, magically finds just the
right data (like a value for the key `:local-store`) in its first `cofx` argument.
So, it is the event handler's Interceptors which put it there.
### Which Interceptors?
If Interceptors put data in `:coeffects`, then we'll need to add the right ones
when we register our event handler.
when we register our event handler.
Something like this (this handler is the same as before, except for one detail):
```clj

View File

@ -29,7 +29,7 @@ re-frame apps are **event driven**.
Event driven apps have this core, perpetual loop:
1. your app is in some quiescent state, patiently waiting for the next event
2. an event arrives (user presses a button, a websocket gets data, etc)
2. an event arrives (because the user presses a button, a websocket gets data, etc)
3. computation/processing follows as the event is handled, leading to changes in app state, the UI, etc
4. Goto 1
@ -49,7 +49,7 @@ With re-frame, step 3 happens like this:
Every single event is processed in the same way. Every single one.
It is like a **four domino sequence**: an event arrives and
It is like a **domino sequence**: an event arrives and
then bang, bang, bang, one domino triggers the next. A delightfully
regular environment to understand and debug!
@ -81,7 +81,7 @@ Below, I suggest a particular combination of technologies which, working togethe
will write a trace to the devtools console. Sorry, but there's no fancy
SVG dashboard. We said simple, right?
First, use clairvoyant to trace function calls and data flow. We've had
First, use `clairvoyant` to trace function calls and data flow. We've had
a couple of Clairvoyant PRs accepted, and they make it work well for us.
We've also written a specific Clairvoyant tracer tuned for our re-frame
needs. https://clojars.org/day8/re-frame-tracer.
@ -172,7 +172,7 @@ And name those event handlers:
You must throw a compile-time switch for tracing to be included into development builds.
If you are using lein, do this in your `project.clj` file:
If you are using `lein`, do this in your `project.clj` file:
```clj
:cljsbuild {:builds [{:id "dev" ;; for the development build, turn on tracing
@ -198,7 +198,7 @@ Do you see the dominos?
If the functions you are tracing take large data-structures as parameters, or
return large values, then you will be asking clairvoyant to push/log a LOT
of data into the js/console. This can take a while and might mean devtools
of data into the `js/console`. This can take a while and might mean devtools
takes a lot of RAM.
For example, if your `app-db` was big and complicated, you might use `path`