mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-22 14:58:12 +00:00
Improvements in newbie documentation
This commit is contained in:
parent
ed79d8e4ac
commit
3beb7cfaf3
@ -7,25 +7,20 @@
|
||||
|
||||
|
||||
;; -- Interceptors --------------------------------------------------------------
|
||||
;; Interceptors are an advanced topic. So, we're plunging into the deep end.
|
||||
;; Interceptors are an advanced topic. So, we're plunging into the deep end here.
|
||||
;;
|
||||
;; There are full tutorials on Interceptors. But I'll try here to get you
|
||||
;; going enough information so that you can proceed without reading those
|
||||
;; docs for the moment.
|
||||
;; There are full tutorials on Interceptors in re-frame's /docs. But to get
|
||||
;; you going, here's a very high level description of them ...
|
||||
;;
|
||||
;; Every event handler can be "wrapped" in a chain of interceptors. Each of these
|
||||
;; interceptors can do things "before" and/or "after" the event handler itself.
|
||||
;; Think of them like the "middleware" that is often used in web servers.
|
||||
;; Interceptors are a useful way of handling crosscutting concerns like
|
||||
;; logging, or debugging, and factoring out commonality.
|
||||
;; interceptors can do things "before" and/or "after" the event handler is executed.
|
||||
;; They are like the "middleware" of web servers, wrapping around the "handler".
|
||||
;; Interceptors are a useful way of factoring out commonality (across event
|
||||
;; handlers) and looking after cross-cutting concerns like logging or validation.
|
||||
;;
|
||||
;; They are also used to "inject" values into the `coeffects` parameter of
|
||||
;; an event handler, when that handler needs access to certain resources.
|
||||
;;
|
||||
;; Yeah, so that's just enough information to get you going. But read the
|
||||
;; /docs for full information. This is an advanced topic.
|
||||
;;
|
||||
;;
|
||||
|
||||
(defn check-and-throw
|
||||
"Throws an exception if `db` doesn't match the Spec `a-spec`."
|
||||
@ -34,19 +29,19 @@
|
||||
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
|
||||
|
||||
;; Event handlers change state, that's their job. But what happens if there's
|
||||
;; a bug in the event handler which corrupts application state in some subtle way?
|
||||
;; Next, we create an interceptor `check-spec-interceptor`.
|
||||
;; a bug in the event handler and it corrupts application state in some subtle way?
|
||||
;; Next, we create an interceptor called `check-spec-interceptor`.
|
||||
;; Later, we use this interceptor in the interceptor chain of all event handlers.
|
||||
;; When included in the interceptor chain of an event handler, this interceptor
|
||||
;; runs `check-and-throw` `after` the event handler has finished, checking
|
||||
;; the contents of `app-db` against a spec.
|
||||
;; If the event handler messed up `app-db` an exception will be thrown. This
|
||||
;; helps us detect event handler bugs early.
|
||||
;; Because all state is held in `app-db`, we are effectively checking the
|
||||
;; ENTIRE state of the application after each event handler runs.
|
||||
;; the value in `app-db` against a spec.
|
||||
;; If the event handler corrupted tha value in `app-db` an exception will be
|
||||
;; thrown. This helps us detect event handler bugs early.
|
||||
;; Because all state is held in `app-db`, we are effectively validating the
|
||||
;; ENTIRE state of the application after each event handler runs. All of it.
|
||||
(def check-spec-interceptor (after (partial check-and-throw :todomvc.db/db)))
|
||||
|
||||
;; Part of the TodoMVC challenge is to remember todos in Local Storage.
|
||||
;; Part of the TodoMVC challenge is to store todos in Local Storage.
|
||||
;; Next, we define an interceptor to help with this challenge.
|
||||
;; This interceptor runs `after` an event handler, and it stores the
|
||||
;; current todos into local storage.
|
||||
@ -87,34 +82,40 @@
|
||||
;; 2. The default initial value
|
||||
;;
|
||||
;; Advanced topic: we inject the todos currently stored in LocalStore
|
||||
;; into the first, coeffect parameter via `(inject-cofx :local-store-todos)`
|
||||
;; To fully understand how that works, you'll have to review the tutorials.
|
||||
;; If you are interested, look at the bottom of `db.cljs` to see how this is done.
|
||||
;; into the first, coeffect parameter via use of the interceptor
|
||||
;; `(inject-cofx :local-store-todos)`
|
||||
;;
|
||||
(reg-event-fx ;; part of the re-frame API
|
||||
:initialise-db ;; event id being handled
|
||||
[(inject-cofx :local-store-todos) ;; <-- advanced: obtain todos from localstore
|
||||
check-spec-interceptor] ;; after the event handler runs, check that app-db matches the spec
|
||||
(fn [{:keys [db local-store-todos]} _] ;; the handler being registered
|
||||
;; To fully understand how this works, you'll have to review the tutorials
|
||||
;; and look at the bottom of `db.cljs` for the `:local-store-todos` cofx
|
||||
;; registration.
|
||||
(reg-event-fx ;; part of the re-frame API
|
||||
:initialise-db ;; event id being handled
|
||||
|
||||
;; the interceptor chain (a vector of interceptors)
|
||||
[(inject-cofx :local-store-todos) ;; get todos from localstore, and put into coeffects arg
|
||||
check-spec-interceptor] ;; after the event handler runs, check app-db matches Spec
|
||||
|
||||
;; the event handler (function) being registered
|
||||
(fn [{:keys [db local-store-todos]} _] ;; take 2 vals from coeffects. Ignore event vector itself.
|
||||
{:db (assoc default-value :todos local-store-todos)})) ;; all hail the new state
|
||||
|
||||
|
||||
;; usage: (dispatch [:set-showing :active])
|
||||
;; This event is dispatched when the user clicks on the various
|
||||
;; filter buttons at the bottom of the panel. All, active or done.
|
||||
;; This event is dispatched when the user clicks on one of the 3
|
||||
;; filter buttons at the bottom of the display.
|
||||
(reg-event-db ;; part of the re-frame API
|
||||
:set-showing ;; event-id
|
||||
[check-spec-interceptor]
|
||||
(fn [db [_ new-filter-kw]]
|
||||
(fn [db [_ new-filter-kw]] ;; new-filter-kw is one of :all, :active or :done
|
||||
(assoc db :showing new-filter-kw)))
|
||||
|
||||
;; NOTE: here is a rewrite of the event handler above using `path` or `trimv`
|
||||
;; These interceptors can be interesting and useful, but they are an advanced topic.
|
||||
;; It will be illuminating to compare the version below with the one above.
|
||||
;; These interceptors are useful, but they are an advanced topic.
|
||||
;; It will be illuminating if you compare this rewrite with the original above.
|
||||
#_(reg-event-db
|
||||
:set-showing ;; event-id
|
||||
|
||||
;; this chain of 3 interceptors wrap the handler. Note use of path and trimv
|
||||
;; this chain of 3 interceptors wrap the handler. Note use of `path` and `trimv`
|
||||
[check-spec-interceptor (path :showing) trim-v]
|
||||
|
||||
;; The event handler
|
||||
@ -127,21 +128,21 @@
|
||||
new-filter-kw)) ;; return new state for the path
|
||||
|
||||
|
||||
;; usage: (dispatch [:add-todo "a string"])
|
||||
;; usage: (dispatch [:add-todo "a description string"])
|
||||
(reg-event-db ;; given the text, create a new todo
|
||||
:add-todo
|
||||
|
||||
;; The standard set of interceptors, defined above, which we
|
||||
;; apply to all todos-modifying event handlers. Looks after
|
||||
;; writing todos to local store, etc.
|
||||
;; NOTE: the interceptors include `path` and `trimv`
|
||||
;; use for all todos-modifying event handlers. Looks after
|
||||
;; writing todos to LocalStore, etc.
|
||||
;; NOTE: this chain includes `path` and `trimv`
|
||||
todo-interceptors
|
||||
|
||||
;; The event handler function.
|
||||
;; The "path" interceptor in `todo-interceptors` means 1st parameter is the
|
||||
;; value at `:todos` within `db`, rather than the full `db`.
|
||||
;; value at `:todos` path within `db`, rather than the full `db`.
|
||||
;; And, further, it means the event handler returns just the value to be
|
||||
;; put into `:todos` and not the entire `db`.
|
||||
;; put into `:todos` path, and not the entire `db`.
|
||||
;; So, a path interceptor makes the event handler act more like clojure's `update-in`
|
||||
(fn [todos [text]] ;; because of trimv, the 2nd parameter is NOT [_ text]
|
||||
(let [id (allocate-next-id todos)]
|
||||
|
@ -4,13 +4,13 @@
|
||||
;; -------------------------------------------------------------------------------------
|
||||
;; Layer 2
|
||||
;;
|
||||
;; https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md
|
||||
;; See https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md
|
||||
;;
|
||||
;; Layer 2 query functions, are "extractors". They simply take from `app-db`
|
||||
;; and don't do any further computation on the extracted values. That further
|
||||
;; computation happens in Layer 3.
|
||||
;; Why? Well Layer 2 subscriptions will rerun every time that `app-db` changes.
|
||||
;; So for efficiency reasons, we want them to be trivial extractors.
|
||||
;; Layer 2 query functions, are "extractors". They take from `app-db`
|
||||
;; and don't do any further computation on the extracted values. Any further
|
||||
;; computation should happen in Layer 3.
|
||||
;; Why? It is an efficiency thing. Every Layer 2 subscriptions will rerun anytime
|
||||
;; that `app-db` changes (in any way). As a result, we want Layer 2 to be trivial.
|
||||
;;
|
||||
(reg-sub
|
||||
:showing ;; usage: (subscribe [:showing])
|
||||
@ -30,7 +30,7 @@
|
||||
;; -------------------------------------------------------------------------------------
|
||||
;; Layer 3
|
||||
;;
|
||||
;; https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md
|
||||
;; See https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md
|
||||
;;
|
||||
;; A subscription handler is a function which is re-run when its input signals
|
||||
;; change. Each time it is rerun, it produces a new output (return value).
|
||||
@ -90,8 +90,9 @@
|
||||
(reg-sub
|
||||
:visible-todos
|
||||
|
||||
;; signal function - tells us what inputs flow into this node
|
||||
;; returns a vector of two input signals
|
||||
;; Signal Function
|
||||
;; Tells us what inputs flow into this node.
|
||||
;; Returns a vector of two input signals (in this case)
|
||||
(fn [query-v _]
|
||||
[(subscribe [:todos])
|
||||
(subscribe [:showing])])
|
||||
@ -107,16 +108,20 @@
|
||||
;; -------------------------------------------------------------------------------------
|
||||
;; Hey, wait on!!
|
||||
;;
|
||||
;; How did those two simple registrations at the top work?
|
||||
;; How did those two simple Layer 2 registrations at the top work?
|
||||
;; We only supplied one function in those registrations, not two?
|
||||
;; Very observant of you, I'm glad you asked.
|
||||
;; When the signal-returning-fn is omitted, re-sub provides a default,
|
||||
;; When the signal-returning-fn is omitted, reg-sub provides a default,
|
||||
;; and it looks like this:
|
||||
;; (fn [_ _] re-frame.db/app-db)
|
||||
;; (fn [_ _]
|
||||
;; re-frame.db/app-db)
|
||||
;; It returns one signal, and that signal is app-db itself.
|
||||
;;
|
||||
;; So the two simple registrations at the top didn't need to provide a signal-fn,
|
||||
;; because they operated only on the value in app-db, supplied as 'db' in the 1st argument.
|
||||
;;
|
||||
;; So that, by the way, is why Layer 2 subscriptions always re-calculate when `app-db`
|
||||
;; changes - `app-db` is literally their input signal.
|
||||
|
||||
;; -------------------------------------------------------------------------------------
|
||||
;; SUGAR ?
|
||||
|
@ -16,27 +16,26 @@
|
||||
[clojure.set :as set]))
|
||||
|
||||
|
||||
;; This namespace defines the re-frame API
|
||||
|
||||
;; When originally writing this re-frame API namespace, we used
|
||||
;; this technique:
|
||||
;; (def api-name (deeper.namespace/where-the-defn-is))
|
||||
;; -- API ---------------------------------------------------------------------
|
||||
;;
|
||||
;; Turns out this technique makes it hard:
|
||||
;; When originally writing this namespace, we used this technique:
|
||||
;; (def api-name-for-fn deeper.namespace/where-the-defn-is)
|
||||
;;
|
||||
;; Turns out, not doing a `defn` in the API itself makes it hard:
|
||||
;; - to auto-generate API docs
|
||||
;; - for IDEs to provide code completion on functions in the API.
|
||||
;; - for IDEs to provide code completion on functions in the API
|
||||
;;
|
||||
;; Which is annoying. But there are pros and cons and we haven't yet
|
||||
;; revisited the decision. So, sorry, in advance. To compensate we've
|
||||
;; added more nudity to the official docs.
|
||||
|
||||
|
||||
;; -- dispatch ---------------------------------------------------------------
|
||||
;; -- dispatch ----------------------------------------------------------------
|
||||
(def dispatch router/dispatch)
|
||||
(def dispatch-sync router/dispatch-sync)
|
||||
|
||||
|
||||
;; -- subscriptions ----------------------------------------------------------
|
||||
;; -- subscriptions -----------------------------------------------------------
|
||||
(def reg-sub subs/reg-sub)
|
||||
(def subscribe subs/subscribe)
|
||||
|
||||
@ -61,7 +60,7 @@
|
||||
(def clear-cofx (partial registrar/clear-handlers cofx/kind)) ;; think unreg-cofx
|
||||
|
||||
|
||||
;; -- Events -----------------------------------------------------------------
|
||||
;; -- Events ------------------------------------------------------------------
|
||||
|
||||
(defn reg-event-db
|
||||
"Register the given event `handler` (function) for the given `id`. Optionally, provide
|
||||
|
Loading…
x
Reference in New Issue
Block a user