Gardening passes over docs
This commit is contained in:
parent
3bcba7b796
commit
108ee61220
|
@ -7,7 +7,7 @@ Yes, a surprising claim.
|
|||
|
||||
Events "happen" when they are dispatched.
|
||||
|
||||
So, this causes an event:
|
||||
So, this makes an event happen:
|
||||
```clj
|
||||
(dispatch [:set-flag true])
|
||||
```
|
||||
|
@ -26,7 +26,7 @@ And that state change is very desirable. Without the state change our
|
|||
application can't incorporate that button click, or the newly arrived
|
||||
websocket message. Without mutation, the apps just sits there, stuck.
|
||||
|
||||
State change is how the application "moves forward" - how it does its job.
|
||||
State change is how the application "moves forward" - how it does its job. Useful!
|
||||
|
||||
On the other hand, control logic and state mutation tend to be the most
|
||||
complex and error prone of part of an app.
|
||||
|
@ -39,24 +39,24 @@ provided you with a simple programming model.
|
|||
It said you should call `reg-event-db` to associate an event id,
|
||||
with a function to do the handling:
|
||||
```clj
|
||||
(re-frame.core/reg-event-db
|
||||
:set-flag
|
||||
(fn [db [_ new-value]
|
||||
(re-frame.core/reg-event-db ;; <-- call this to register handlers
|
||||
:set-flag ;; this is an event id
|
||||
(fn [db [_ new-value] ;; this function does the handling
|
||||
(assoc db :flag new-value)))
|
||||
```
|
||||
|
||||
The function you register, actions the event.
|
||||
The function you register, handles events with a given `id`.
|
||||
|
||||
And that handler `fn` was expected to be pure. Given the
|
||||
value in `app-db` as the first argument, and the event (including params)
|
||||
as the second argument, it is expected to provide the new value for `app-db`.
|
||||
And that handler `fn` is expected to be pure. Given the
|
||||
value in `app-db` as the first argument, and the event (vector)
|
||||
as the second argument, it is expected to provide a new value for `app-db`.
|
||||
|
||||
Data in, a computation and data out. Pure.
|
||||
|
||||
### 90% Solution
|
||||
|
||||
This paradigm provides a lovely solution 90% of the time, but there are times
|
||||
when it fails us.
|
||||
when it isn't enough.
|
||||
|
||||
Here's an example from the messy 10%. To get its job done, this handler has to side effect:
|
||||
```clj
|
||||
|
@ -67,11 +67,11 @@ Here's an example from the messy 10%. To get its job done, this handler has to s
|
|||
(assoc db :send-spam new-val)))
|
||||
```
|
||||
|
||||
That `dispatch` queues up another event to be processed. It changes the world.
|
||||
That `dispatch` queues up another event to be processed. It changes the world.
|
||||
|
||||
Just to be clear, this code works. The handler returns a new version of `db`, so tick,
|
||||
and that `dispatch` will itself be "handled" asynchronously
|
||||
very shortly after this handler finishes, double tick.
|
||||
very shortly after this handler finishes, double tick.
|
||||
|
||||
So, you can "get away with it". But it ain't pure.
|
||||
|
||||
|
@ -99,7 +99,7 @@ consequences:
|
|||
using the right URL? "mocking" should be is mocked. It is a code smell.
|
||||
3. And event replay-ability is lost.
|
||||
|
||||
Regarding the 3rd point, a re-frame application proceeds step by step, like a reduce. From the README:
|
||||
Regarding the 3rd point above, a re-frame application proceeds step by step, like a reduce. From the README:
|
||||
|
||||
> at any one time, the value in app-db is the result of performing a reduce over the entire
|
||||
> collection of events dispatched in the app up until that time. The combining
|
||||
|
@ -112,12 +112,12 @@ replay, inserting extra events into it, etc, which ruins the process.
|
|||
|
||||
### The Other Problem
|
||||
|
||||
Here's another purity problem:
|
||||
And there's another purity problem:
|
||||
```clj
|
||||
(reg-event-db
|
||||
:load-localstore
|
||||
(fn [db _]
|
||||
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <--
|
||||
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
|
||||
(assoc db :defaults val))))
|
||||
```
|
||||
|
||||
|
@ -127,13 +127,13 @@ So this handler has no side effect - it doesn't need to change the world - __but
|
|||
need to source data from somewhere other than its arguments - from somewhere
|
||||
outside of app-db or the event.
|
||||
|
||||
It isn't a pure function which leads to the normal problems.
|
||||
So, it isn't a pure function, and that leads to the normal problems.
|
||||
|
||||
### Effects And Coeffects
|
||||
|
||||
So there are [two concepts at play here](http://tomasp.net/blog/2014/why-coeffects-matter/):
|
||||
- **Effects** - what your event handler does to the world (aka side-effects)
|
||||
- **Coeffects** - what your event handler requires from the world (aka [side-causes](http://blog.jenkster.com/2015/12/what-is-functional-programming.html))
|
||||
- **Coeffects** - the data your event handler requires from the world in order to do its computation (aka [side-causes](http://blog.jenkster.com/2015/12/what-is-functional-programming.html))
|
||||
|
||||
We'll need a solution for both.
|
||||
|
||||
|
@ -149,7 +149,7 @@ There's just no getting away from living in a mutative world, er,
|
|||
which sounds ominous. Is that it? Are we doomed to impurity?
|
||||
|
||||
Well, luckily a small twist in the tale makes a profound difference. We
|
||||
will look at side effects first. Instead of creating event handlers
|
||||
will look at side-effects first. Instead of creating event handlers
|
||||
which *do side-effects*, we'll instead get them to *cause side-effects*.
|
||||
|
||||
### Doing vs Causing
|
||||
|
@ -159,28 +159,28 @@ Above, I proudly claimed that this `fn` event handler was pure:
|
|||
(reg-event-db
|
||||
:my-event
|
||||
(fn [db _]
|
||||
(assoc db :flag true)))
|
||||
(assoc db :flag true)))
|
||||
```
|
||||
|
||||
Takes a `db` value, computes and returns a `db` value. No coeffects or effects. Yep, that's Pure!
|
||||
|
||||
All true, but ... this purity is only possible because re-frame is doing
|
||||
Yes, all true, but ... this purity is only possible because re-frame is doing
|
||||
the necessary side-effecting.
|
||||
|
||||
Wait on. What "necessary side-effecting"?
|
||||
|
||||
Well, application state is stored in `app-db`, right? And it is a ratom. After
|
||||
Well, application state is stored in `app-db`, right? And it is a ratom. And after
|
||||
each event handler runs, it must be `reset!` to the newly returned
|
||||
value. That, right there, is the necessary side effecting.
|
||||
value. That, right there, is the "necessary side effecting".
|
||||
|
||||
We get to live in our ascetic functional world because re-frame is
|
||||
looking after the "necessary side-effects" on `app-db`. Interesting.
|
||||
looking after the "necessary side-effects" on `app-db`.
|
||||
|
||||
### Et tu, React?
|
||||
|
||||
Turns out it's the same pattern with Reagent/React.
|
||||
|
||||
We get to write a nice pure component:
|
||||
We get to write a nice pure component, like:
|
||||
```clj
|
||||
(defn say-hi
|
||||
[name]
|
||||
|
@ -191,9 +191,9 @@ after the "necessary side-effects".
|
|||
|
||||
### Pattern Structure
|
||||
|
||||
Pause and look back at `say-hi`. I'd like you to view it through the following lens: it is a pure
|
||||
function which **returns a description of the side-effects required**. It says: add a div element
|
||||
to the DOM.
|
||||
Pause and look back at `say-hi`. I'd like you to view it through the
|
||||
following lens: it is a pure function which **returns a description
|
||||
of the side-effects required**. It says: add a div element to the DOM.
|
||||
|
||||
Notice that the description is declarative. We don't tell React how to do it.
|
||||
|
||||
|
@ -203,7 +203,7 @@ This is a big, important concept. While we can't get away from certain side-eff
|
|||
program using pure functions which **describe side-effects, declaratively, in data** and
|
||||
let the backing framework look after the "doing" of them. Efficiently. Discreetly.
|
||||
|
||||
Let's use this pattern to solve the side-effecting handler problem.
|
||||
Let's use this pattern to solve the side-effecting event-handler problem.
|
||||
|
||||
### The Two Step Plan
|
||||
|
||||
|
@ -235,8 +235,8 @@ Here it is re-written so as to be pure:
|
|||
```
|
||||
|
||||
Notes: <br>
|
||||
*<1>* we're using `reg-event-fx` instead of `reg-event-db` to register (that's `-db` bs `-fx`) <br>
|
||||
*<2>* the first parameter is nolonger just `db`. It is a map from which
|
||||
*<1>* we're using `reg-event-fx` instead of `reg-event-db` to register (that's `-db` vs `-fx`) <br>
|
||||
*<2>* the first parameter is no longer just `db`. It is a map from which
|
||||
[we are destructuring db](http://clojure.org/guides/destructuring). Ie.
|
||||
it is a map which contains a `:db` key. <br>
|
||||
*<3>* The handler is returning a data structure (map) which describes two side-effects:
|
||||
|
@ -287,21 +287,21 @@ So far we've written our new style `-fx handlers like this:
|
|||
{ ... }))
|
||||
```
|
||||
|
||||
It is now time to name that first parameter:
|
||||
It is now time to name that first argument:
|
||||
```clj
|
||||
(reg-event-fx
|
||||
:my-event
|
||||
(fn [coeffects event] ;; <--- it has a name
|
||||
(fn [coeffects event] ;; <--- thy name be coeefects
|
||||
{ ... }))
|
||||
```
|
||||
|
||||
When you use the `-fx` form of registration, the first argument of your handler will be a coeffects map.
|
||||
|
||||
In that map will be the complete set of "inputs" required by your function. The complete
|
||||
set of computational resources (data) which it needs to perform its computation. But how?
|
||||
I'll explain soon enough, I promise, but for the moment, take it as a magical given.
|
||||
set of computational resources (data) needed to perform its computation. But how?
|
||||
I'll explain in an upcoming tutorial, I promise, but for the moment, take it as a magical given.
|
||||
|
||||
One of the keys in `coeffects` will likely be `:db` and that will be the value in app-db.
|
||||
One of the keys in `coeffects` will likely be `:db` and that will be the value of `app-db`.
|
||||
|
||||
Remember this impure handler from before:
|
||||
```clj
|
||||
|
@ -318,13 +318,15 @@ We'd now rewrite that as a pure handler, like this:
|
|||
:load-localstore
|
||||
(fn [coeffect _] ;; coeffect is a map containing inputs
|
||||
(let [defaults (:defaults-key coeffect)] ;; <-- use it here
|
||||
(assoc ( :defaults defaults))))
|
||||
{:db (assoc (:db coeffects) :defaults defaults)}))) ;; returns effects map
|
||||
```
|
||||
|
||||
So, by some magic, not yet revealed, LocalStore will be queried before
|
||||
this handler runs and the required value from it will be placed into
|
||||
`coeffects` under the key `:localstore` for the handler to use.
|
||||
|
||||
That process leaves the handler itself pure because it only sources data from arguments.
|
||||
|
||||
|
||||
### Variations On A Theme
|
||||
|
||||
|
@ -350,16 +352,18 @@ and
|
|||
{:db (assoc (:db context) :flag new-value)}))
|
||||
```
|
||||
|
||||
Obviously the `-db` variation is simpler.
|
||||
Obviously the `-db` variation is simpler and you'd use it whenever you
|
||||
can. The `-fx` version is more flexible, so it will sometimes have its place.
|
||||
|
||||
|
||||
### Summary
|
||||
|
||||
90% of the time, simple `-db` handlers are the tool to use.
|
||||
90% of the time, simple `-db` handlers are the right tool to use.
|
||||
|
||||
But about 10% of the time, our handlers need additional inputs (coeffecs) or they need to
|
||||
cause additional side-effects (effects). That's when you reach for `-fx` handlers.
|
||||
|
||||
To take the next step, we need now to shine a light on `interceptors` which provide
|
||||
the mechanism by which event handlers are executed.
|
||||
In the next tutorial, we'll shine a light on `interceptors` which are
|
||||
the mechanism by which event handlers are executed. That knowledge will give us a springboard
|
||||
to more deeply understand coeffects and effects. We'll soon be writing our own.
|
||||
|
||||
|
|
|
@ -20,13 +20,13 @@ make side effects a noop in tests.
|
|||
* [What Makes This Work?](#what-makes-this-work-)
|
||||
* [Order Of Effects?](#order-of-effects-)
|
||||
* [Noops](#noops)
|
||||
- [Builtin Effects Handlers](#builtin-effects-handlers)
|
||||
- [Builtin Effect Handlers](#builtin-effect-handlers)
|
||||
+ [:dispatch-later](#-dispatch-later)
|
||||
+ [:dispatch](#-dispatch)
|
||||
+ [:dispatch-n](#-dispatch-n)
|
||||
+ [:deregister-event-handler](#-deregister-event-handler)
|
||||
+ [:db](#-db)
|
||||
- [Other Available Effects](#other-available-effects)
|
||||
- [External Effects](#external-effects)
|
||||
|
||||
## Effects
|
||||
### Where Effects Come From
|
||||
|
@ -53,7 +53,7 @@ the particular side effect required, and the `value` for that `key` provides
|
|||
further information. The structure of the `value` varies with the side
|
||||
effect itself.
|
||||
|
||||
Here's two instructions:
|
||||
Here's the two instructions from the example above:
|
||||
```cljs
|
||||
{:db (assoc db :flag a) ;; side effect on app-db
|
||||
:dispatch [:do-something-else 3]} ;; dispatch this event
|
||||
|
@ -65,9 +65,8 @@ That `:db` `key` instructs that "app-db" should be reset to the
|
|||
That `:dispatch` `key` instructs that an event should be
|
||||
dispatched. The `value` is the vector to dispatch.
|
||||
|
||||
In addition to those two shown above, there's many other possible
|
||||
effects, like for example `dispatch-n` whose value is
|
||||
expected to be a collection of vectors to dispatch.
|
||||
There's many other possible
|
||||
effects, like for example `:dispatch-later` or `:set-local-store`.
|
||||
|
||||
And so on. And so on. Which brings us to a problem.
|
||||
|
||||
|
@ -78,14 +77,14 @@ possible effects is open ended.
|
|||
|
||||
What if you use Postgress and want an effect which issues mutating
|
||||
queries? Or what if you want to send logs to Logentries.
|
||||
Or write values to a LocalStore key. And what happens if your database is
|
||||
Or write values to windows.location. And what happens if your database is
|
||||
X, Y or Z?
|
||||
|
||||
The list is long and varied, with everyone using a different combination
|
||||
of available effects.
|
||||
|
||||
So effect handling has to be extensible. You need to a way to define
|
||||
your own side effects.
|
||||
your own side effects.
|
||||
|
||||
### Extensible Side Effects
|
||||
|
||||
|
@ -104,7 +103,7 @@ __<1>__ the key for the effect. When an effects map contains
|
|||
the key `:butterfly`, the registered function should be used to action it. <br>
|
||||
|
||||
__<2>__ a function which actions the side effect. It is called
|
||||
with one argument - the value for this key, in the effects map.
|
||||
with one argument - the value for this key, in the effects map.
|
||||
|
||||
So, if an event handler returned these effects:
|
||||
```clj
|
||||
|
@ -115,20 +114,23 @@ So, if an event handler returned these effects:
|
|||
Then the function we registered for `:butterfly` would be called to handle that effect. And it would be called with the parameter "Flapping".
|
||||
|
||||
So, terminology:
|
||||
- `:butterfly` is known as an "effect key"
|
||||
- and the function registered is known as an "effect handler"
|
||||
- `:butterfly` is an "effect key"
|
||||
- and the function registered is an "effect handler"
|
||||
|
||||
### Writing An Effect Handler
|
||||
|
||||
A word of advice - make them as simple as possible, and then simplify them further. You don't want them containing any fancy logic.
|
||||
A word of advice - make them as simple as possible, and then
|
||||
simplify them further. You don't want them containing any fancy logic.
|
||||
|
||||
Why? Well, because they are all side-effecty they will be a pain to test rigorously. And the combination of fancy logic and limited testing always ends in tears.
|
||||
Why? Well, because they are all side-effecty they will be a pain
|
||||
to test rigorously. And the combination of fancy logic and limited
|
||||
testing always ends in tears.
|
||||
|
||||
A second word of advice - when you create an effect handler,
|
||||
you also have to design (and document!) the structure of the
|
||||
`value` expected.
|
||||
|
||||
Realise that you are creating a nano DSL when designing this `value` and
|
||||
When you do, realise that you are creating a nano DSL for `value` and try to
|
||||
make it simple too. Resist tricky. Create as little cognitive overhead as possible
|
||||
for the eventual readers of your effectful code.
|
||||
|
||||
|
@ -152,31 +154,36 @@ An effects map does not have to include the `effect key` `:db`.
|
|||
It is perfectly okay to have effects which do not cause
|
||||
an update to `app-db`.
|
||||
|
||||
In fact, it is perfectly okay for an event handler to return
|
||||
an effects of `{}`. Slightly puzzling, but not a problem.
|
||||
|
||||
|
||||
### What Makes This Work?
|
||||
|
||||
A silently inserted interceptor.
|
||||
|
||||
Whenever you register an event handler via __either__ `reg-event-db`
|
||||
Whenever you register an event handler via __either__ `reg-event-db`
|
||||
or `reg-event-fx`, an interceptor, cunningly named `do-effects`,
|
||||
is inserted at the beginning of the chain.
|
||||
|
||||
or `reg-event-fx`, an interceptor, cunningly named `do-effects`, is inserted at the beginning of the chain.
|
||||
|
||||
So, if your event handler registration looked like this:
|
||||
Example: if your event handler registration looked like this:
|
||||
```clj
|
||||
(reg-event-fx
|
||||
:some-id
|
||||
[debug (path :right)] ;; <-- two interceptors, apparently
|
||||
(fn [coeffect]
|
||||
{}) ;; <-- return effects here
|
||||
{}) ;; <-- imagine returned effects here
|
||||
```
|
||||
|
||||
You have actually registered with 3 interceptors:
|
||||
While it might look like you have registered with 2 interceptors,
|
||||
`reg-event-fx` will make it 3:
|
||||
```clj
|
||||
[do-effects debug (path :right)]
|
||||
```
|
||||
|
||||
`do-effects` placement at the beginning of the interceptor chain means it's `:after` function is the final act
|
||||
in the chain's first-forwards-and-then-backwards execution.
|
||||
`do-effects` placement at the beginning of the interceptor chain means
|
||||
it's `:after` function would be the final act when the chain is executed
|
||||
(forwards and then backwards, as described in the Interceptor Tutorial).
|
||||
|
||||
In this final act, the `:after` function extracts `:effects` from `context`
|
||||
and simply iterates across the key/value pairs it contains, calling the
|
||||
|
@ -197,17 +204,19 @@ of the interceptor chain. It is only a few lines of code.
|
|||
|
||||
There isn't one.
|
||||
|
||||
`do-effects` does not currently allow you to control the order in
|
||||
`do-effects` does not currently provide you with control over the order in
|
||||
which side effects occur. The `:db` side effect
|
||||
might happen before `:dispatch`, or not.
|
||||
might happen before `:dispatch`, or not.
|
||||
|
||||
If you feel you need ordering, then please
|
||||
open an issue and explain the usecase. It is the current absence of
|
||||
good usecases which have lead to this not being implemented.
|
||||
*Note:* if you feel you need ordering, then please
|
||||
open an issue and explain the usecase. The current absence of
|
||||
good usecases is the reason it isn't implemented. Give us some and
|
||||
we'll revisit.
|
||||
|
||||
If ever ordering was needed, it might be handled via metadata on `:effects`. And
|
||||
perhaps allow `reg-fx` to optionally take two functions:
|
||||
- an effects pre-process fn <--- new
|
||||
*Further Note:* if ever ordering was needed, it might be handled via
|
||||
metadata on `:effects`. Also, perhaps allow `reg-fx` to optionally
|
||||
take two functions:
|
||||
- an effects pre-process fn <--- new. Takes `:effects` returns `:effects`
|
||||
- the effects handler, as already discussed.
|
||||
|
||||
Anyway, these are all just possibilities. But not needed or implemented yet.
|
||||
|
@ -233,7 +242,7 @@ XXX talk about reinstating:
|
|||
|
||||
|
||||
|
||||
## Builtin Effects Handlers
|
||||
## Builtin Effect Handlers
|
||||
|
||||
#### :dispatch-later
|
||||
|
||||
|
@ -289,7 +298,7 @@ usage:
|
|||
{:db {:key1 value1 key2 value2}}
|
||||
```
|
||||
|
||||
## Other Available Effects
|
||||
## External Effects
|
||||
|
||||
- https://github.com/Day8/re-frame-http-fx (GETs and POSTs)
|
||||
- https://github.com/Day8/re-frame-forward-events-fx (slightly exotic)
|
||||
|
|
Loading…
Reference in New Issue