mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-22 14:58:12 +00:00
Docs massaging
This commit is contained in:
parent
c2492494a8
commit
e59168e441
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
***
|
||||
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
Loading…
x
Reference in New Issue
Block a user