mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-23 15:28:09 +00:00
More on Effects doc - including adding TOC. Want to see it work on Github.
This commit is contained in:
parent
db9aa06de9
commit
401d1baa76
226
docs/Effects.md
226
docs/Effects.md
@ -6,6 +6,8 @@ This tutorial explains how side effects are actioned,
|
||||
how you can create your own side effects, and how you can
|
||||
make side effects a noop in tests.
|
||||
|
||||
[TOC]
|
||||
|
||||
### Where Effects Come From
|
||||
|
||||
When an event handler is registered via `reg-event-fx`, it returns effects.
|
||||
@ -25,9 +27,9 @@ Like this:
|
||||
|
||||
An effects map contains instructions.
|
||||
|
||||
Each key/value pair in the map is one instruction - the key identifies
|
||||
the particular side effect required, and the value for that key provides
|
||||
further information. The structure of the value varies with the side
|
||||
Each key/value pair in the map is one instruction - the `key` identifies
|
||||
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:
|
||||
@ -36,31 +38,34 @@ Here's two instructions:
|
||||
:dispatch [:do-something-else 3]} ;; dispatch this event
|
||||
```
|
||||
|
||||
That `:db` key instructs that "app-db" should be reset to the
|
||||
value supplied.
|
||||
That `:db` `key` instructs that "app-db" should be reset to the
|
||||
`value` supplied for that `key`.
|
||||
|
||||
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 other possible
|
||||
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 vector of vectors to dispatch.
|
||||
|
||||
expected to be a collection of vectors to dispatch.
|
||||
|
||||
And so on. And so on. Which brings us to a problem.
|
||||
|
||||
|
||||
### Infinite Possible Effects
|
||||
|
||||
While re-frame supplies a some builtin effects, the set of
|
||||
While re-frame supplies a number of builtin effects, the set of
|
||||
possible effects is open ended.
|
||||
|
||||
|
||||
What if you use Postgress and want an effect which issues mutating
|
||||
queries? On the other hand, maybe you want to send logs to Logentries.
|
||||
Or write values to a LocalStore key.
|
||||
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
|
||||
X, Y or Z?
|
||||
|
||||
The list is long, and everyone will have different needs.
|
||||
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.
|
||||
|
||||
So effect handling has to be extensible. You need to a way to define your own side effects.
|
||||
|
||||
### Extensible Side Effects
|
||||
|
||||
re-frame provides a function `reg-fx` through which you can register your own `Effect Handlers`.
|
||||
@ -68,50 +73,46 @@ re-frame provides a function `reg-fx` through which you can register your own `E
|
||||
Use it like this:
|
||||
```clj
|
||||
(reg-fx ;; <-- registration function
|
||||
:my-key ;; <1>
|
||||
:butterfly ;; <1>
|
||||
(fn [value] ;; <2>
|
||||
...
|
||||
))
|
||||
```
|
||||
|
||||
|
||||
__<1>__ the key for the effect. When an effects map contains
|
||||
the key `:my-key`, the registered function should be used to action it. <br>
|
||||
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.
|
||||
|
||||
So, if an event handler returned these effects:
|
||||
```clj
|
||||
{:dispatch [:ev1 42]
|
||||
:my-key "blah"}
|
||||
{:dispatch [:save-maiden 42]
|
||||
:butterfly "Flapping"} ;; butterfly effect !!
|
||||
```
|
||||
|
||||
Then the function we registered for `:my-key` would be called to handle that effect. And it would be
|
||||
called with the parameter "blah".
|
||||
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:
|
||||
- `:my-key` is known as an "effect key"
|
||||
- and the function registered is known as an "effect handler"
|
||||
|
||||
- `:butterfly` is known as an "effect key"
|
||||
- and the function registered is known as 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.
|
||||
|
||||
Why? Well, because they all side-effecty they will be a pain to
|
||||
test rigorously. And the combination of fancy logic
|
||||
and limited testing is ... well, it always ends in tears, right?
|
||||
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.
|
||||
|
||||
A second word of advice - when you create an effect handler,
|
||||
you also have to design (and document!) the structure of the
|
||||
value expected.
|
||||
`value` expected.
|
||||
|
||||
Realise that you are creating a nano DSL when designing this value.
|
||||
Make it simple too. Create as little cognitive overhead as possible
|
||||
Realise that you are creating a nano DSL when designing this `value` and
|
||||
make it simple too. Resist tricky. Create as little cognitive overhead as possible
|
||||
for the eventual readers of your effectful code.
|
||||
|
||||
|
||||
Here's the builtin one for `:db`:
|
||||
Here's the builtin effect handler for `:db`:
|
||||
```clj
|
||||
(reg-fx
|
||||
:db
|
||||
@ -119,22 +120,63 @@ Here's the builtin one for `:db`:
|
||||
(reset! re-frame.db/app-db value)))
|
||||
```
|
||||
|
||||
So, yeah, pretty simple.
|
||||
So, yeah, simple.
|
||||
|
||||
> Note: the return value of an effect handler is ignored.
|
||||
|
||||
### :db Not Always Needed
|
||||
|
||||
An effects map does not have to include the `effect key` :db`.
|
||||
An effects map does not have to include the `effect key` `:db`.
|
||||
|
||||
It is perfectly okay to perform a series of side effects
|
||||
and not update `app-db` in the process.
|
||||
|
||||
### Order Of Effects
|
||||
It is perfectly okay to have effects which do not cause
|
||||
an update to `app-db`.
|
||||
|
||||
|
||||
### What Makes This Work?
|
||||
|
||||
A silently inserted interceptor.
|
||||
|
||||
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.
|
||||
|
||||
So, 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
|
||||
```
|
||||
|
||||
You have actually registered with 3 interceptors:
|
||||
```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.
|
||||
|
||||
In this final act, the `:after` function extracts `:effects` from `context`
|
||||
and simply iterates across the key/value pairs it contains, calling the
|
||||
registered effect handlers for each.
|
||||
|
||||
> For the record, the FISA Court requires that we deny all claims
|
||||
> that `do-effects` is secretly injected NSA surveillance-ware. <br>
|
||||
> We also note that you've been particularly sloppy with your personal
|
||||
> grooming today, including that you forgot to clean your teeth. Again.
|
||||
|
||||
If ever you want to take control of the way effect handling is done,
|
||||
create your own alternative to `reg-event-fx` and, in it, inject
|
||||
your own version of the `do-effects` interceptor at the front
|
||||
of the interceptor chain. It is only a few lines of code.
|
||||
|
||||
|
||||
### Order Of Effects?
|
||||
|
||||
There isn't one.
|
||||
|
||||
re-frame does not allow you to control the order in
|
||||
`do-effects` does not currently allow you to control the order in
|
||||
which side effects occur. The `:db` side effect
|
||||
might happen before `:dispatch`, or not.
|
||||
|
||||
@ -144,37 +186,91 @@ good usecases which have lead to this not being implemented.
|
||||
|
||||
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
|
||||
- the effects handler, as already discussed.
|
||||
- an effects pre-process fn <--- new
|
||||
- the effects handler, as already discussed.
|
||||
|
||||
Anyway, these are all just possibilities. But not needed yet.
|
||||
Anyway, these are all just possibilities. But not needed or implemented yet.
|
||||
|
||||
|
||||
### Noops
|
||||
|
||||
Sometimes when running tests, you need to turn a side effect into a noop.
|
||||
When you are running tests or replaying events, it is sometimes
|
||||
useful to stub out effects.
|
||||
|
||||
Easy. Prove an effect handler which does nothing.
|
||||
This is easily done - you simply register a noop effect handler.
|
||||
|
||||
Want to stub out the `:dispatch` effect? Do this:
|
||||
```clj
|
||||
(reg-fx
|
||||
:my-key
|
||||
(fn [_] )) ;; noop
|
||||
:dispatch
|
||||
(fn [_] )) ;; a noop
|
||||
```
|
||||
|
||||
XXX talk about reinstating:
|
||||
- capture return
|
||||
- XXX new feature?
|
||||
|
||||
### Builtin Effect Handlers
|
||||
## What's Out There Already
|
||||
|
||||
re-frame has these effect handlers built-in:
|
||||
- `:db`
|
||||
- `:dispatch`
|
||||
- `dispatch-n`
|
||||
- XXX
|
||||
|
||||
### What's Out There Already
|
||||
|
||||
- https://github.com/Day8/re-frame-http-fx (GETs and POSTs)
|
||||
- https://github.com/Day8/re-frame-forward-events-fx (slightly exotic)
|
||||
- https://github.com/Day8/re-frame-async-flow-fx (more complicated)
|
||||
- https://github.com/Day8/re-frame-http-fx (GETs and POSTs)
|
||||
- https://github.com/Day8/re-frame-forward-events-fx (slightly exotic)
|
||||
- https://github.com/Day8/re-frame-async-flow-fx (more complicated)
|
||||
|
||||
Create a PR to include yours in this list.
|
||||
|
||||
|
||||
## Builtin Effects Handlers
|
||||
|
||||
#### :dispatch-later
|
||||
|
||||
`dispatch` one or more events after given delays. Expects a collection
|
||||
of maps with two keys: `:ms` and `:dispatch`
|
||||
|
||||
usage:
|
||||
```clj
|
||||
{:dispatch-later [{:ms 200 :dispatch [:event-id "param"]}
|
||||
{:ms 100 :dispatch [:also :this :in :100ms]}]}
|
||||
```
|
||||
|
||||
Which means: in 200ms do this: `(dispatch [:event-id "param"])` and in 100ms ...
|
||||
|
||||
#### :dispatch
|
||||
|
||||
`dispatch` one event. Excepts a single vector.
|
||||
|
||||
usage:
|
||||
```clj
|
||||
{:dispatch [:event-id "param"] }
|
||||
```
|
||||
|
||||
#### :dispatch-n
|
||||
|
||||
`dispatch` more than one event. Expects a collection events.
|
||||
|
||||
usage:
|
||||
```clj
|
||||
{:dispatch-n (list [:do :all] [:three :of] [:these])}
|
||||
```
|
||||
|
||||
#### :deregister-event-handler
|
||||
|
||||
removes a previously registered event handler. Expects either a single id (
|
||||
typically a keyword), or a seq of ids.
|
||||
|
||||
usage:
|
||||
```clj
|
||||
{:deregister-event-handler :my-id)}
|
||||
```
|
||||
or:
|
||||
```clj
|
||||
{:deregister-event-handler [:one-id :another-id]}
|
||||
```
|
||||
|
||||
#### :db
|
||||
|
||||
reset! app-db with a new value. Expects a map.
|
||||
|
||||
usage:
|
||||
```clj
|
||||
{:db {:key1 value1 key2 value2}}
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user