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 --------------------------------------------------------------
|
||||||
;; 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
|
;; There are full tutorials on Interceptors in re-frame's /docs. But to get
|
||||||
;; going enough information so that you can proceed without reading those
|
;; you going, here's a very high level description of them ...
|
||||||
;; docs for the moment.
|
|
||||||
;;
|
;;
|
||||||
;; Every event handler can be "wrapped" in a chain of interceptors. Each of these
|
;; 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.
|
;; interceptors can do things "before" and/or "after" the event handler is executed.
|
||||||
;; Think of them like the "middleware" that is often used in web servers.
|
;; They are like the "middleware" of web servers, wrapping around the "handler".
|
||||||
;; Interceptors are a useful way of handling crosscutting concerns like
|
;; Interceptors are a useful way of factoring out commonality (across event
|
||||||
;; logging, or debugging, and factoring out commonality.
|
;; handlers) and looking after cross-cutting concerns like logging or validation.
|
||||||
;;
|
;;
|
||||||
;; They are also used to "inject" values into the `coeffects` parameter of
|
;; They are also used to "inject" values into the `coeffects` parameter of
|
||||||
;; an event handler, when that handler needs access to certain resources.
|
;; 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
|
(defn check-and-throw
|
||||||
"Throws an exception if `db` doesn't match the Spec `a-spec`."
|
"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)) {}))))
|
(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
|
;; 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?
|
;; a bug in the event handler and it corrupts application state in some subtle way?
|
||||||
;; Next, we create an interceptor `check-spec-interceptor`.
|
;; Next, we create an interceptor called `check-spec-interceptor`.
|
||||||
;; Later, we use this interceptor in the interceptor chain of all event handlers.
|
;; 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
|
;; When included in the interceptor chain of an event handler, this interceptor
|
||||||
;; runs `check-and-throw` `after` the event handler has finished, checking
|
;; runs `check-and-throw` `after` the event handler has finished, checking
|
||||||
;; the contents of `app-db` against a spec.
|
;; the value in `app-db` against a spec.
|
||||||
;; If the event handler messed up `app-db` an exception will be thrown. This
|
;; If the event handler corrupted tha value in `app-db` an exception will be
|
||||||
;; helps us detect event handler bugs early.
|
;; thrown. This helps us detect event handler bugs early.
|
||||||
;; Because all state is held in `app-db`, we are effectively checking the
|
;; Because all state is held in `app-db`, we are effectively validating the
|
||||||
;; ENTIRE state of the application after each event handler runs.
|
;; 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)))
|
(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.
|
;; Next, we define an interceptor to help with this challenge.
|
||||||
;; This interceptor runs `after` an event handler, and it stores the
|
;; This interceptor runs `after` an event handler, and it stores the
|
||||||
;; current todos into local storage.
|
;; current todos into local storage.
|
||||||
@ -87,34 +82,40 @@
|
|||||||
;; 2. The default initial value
|
;; 2. The default initial value
|
||||||
;;
|
;;
|
||||||
;; Advanced topic: we inject the todos currently stored in LocalStore
|
;; Advanced topic: we inject the todos currently stored in LocalStore
|
||||||
;; into the first, coeffect parameter via `(inject-cofx :local-store-todos)`
|
;; into the first, coeffect parameter via use of the interceptor
|
||||||
;; To fully understand how that works, you'll have to review the tutorials.
|
;; `(inject-cofx :local-store-todos)`
|
||||||
;; If you are interested, look at the bottom of `db.cljs` to see how this is done.
|
|
||||||
;;
|
;;
|
||||||
(reg-event-fx ;; part of the re-frame API
|
;; To fully understand how this works, you'll have to review the tutorials
|
||||||
:initialise-db ;; event id being handled
|
;; and look at the bottom of `db.cljs` for the `:local-store-todos` cofx
|
||||||
[(inject-cofx :local-store-todos) ;; <-- advanced: obtain todos from localstore
|
;; registration.
|
||||||
check-spec-interceptor] ;; after the event handler runs, check that app-db matches the spec
|
(reg-event-fx ;; part of the re-frame API
|
||||||
(fn [{:keys [db local-store-todos]} _] ;; the handler being registered
|
: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
|
{:db (assoc default-value :todos local-store-todos)})) ;; all hail the new state
|
||||||
|
|
||||||
|
|
||||||
;; usage: (dispatch [:set-showing :active])
|
;; usage: (dispatch [:set-showing :active])
|
||||||
;; This event is dispatched when the user clicks on the various
|
;; This event is dispatched when the user clicks on one of the 3
|
||||||
;; filter buttons at the bottom of the panel. All, active or done.
|
;; filter buttons at the bottom of the display.
|
||||||
(reg-event-db ;; part of the re-frame API
|
(reg-event-db ;; part of the re-frame API
|
||||||
:set-showing ;; event-id
|
:set-showing ;; event-id
|
||||||
[check-spec-interceptor]
|
[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)))
|
(assoc db :showing new-filter-kw)))
|
||||||
|
|
||||||
;; NOTE: here is a rewrite of the event handler above using `path` or `trimv`
|
;; 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.
|
;; These interceptors are useful, but they are an advanced topic.
|
||||||
;; It will be illuminating to compare the version below with the one above.
|
;; It will be illuminating if you compare this rewrite with the original above.
|
||||||
#_(reg-event-db
|
#_(reg-event-db
|
||||||
:set-showing ;; event-id
|
: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]
|
[check-spec-interceptor (path :showing) trim-v]
|
||||||
|
|
||||||
;; The event handler
|
;; The event handler
|
||||||
@ -127,21 +128,21 @@
|
|||||||
new-filter-kw)) ;; return new state for the path
|
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
|
(reg-event-db ;; given the text, create a new todo
|
||||||
:add-todo
|
:add-todo
|
||||||
|
|
||||||
;; The standard set of interceptors, defined above, which we
|
;; The standard set of interceptors, defined above, which we
|
||||||
;; apply to all todos-modifying event handlers. Looks after
|
;; use for all todos-modifying event handlers. Looks after
|
||||||
;; writing todos to local store, etc.
|
;; writing todos to LocalStore, etc.
|
||||||
;; NOTE: the interceptors include `path` and `trimv`
|
;; NOTE: this chain includes `path` and `trimv`
|
||||||
todo-interceptors
|
todo-interceptors
|
||||||
|
|
||||||
;; The event handler function.
|
;; The event handler function.
|
||||||
;; The "path" interceptor in `todo-interceptors` means 1st parameter is the
|
;; 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
|
;; 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`
|
;; 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]
|
(fn [todos [text]] ;; because of trimv, the 2nd parameter is NOT [_ text]
|
||||||
(let [id (allocate-next-id todos)]
|
(let [id (allocate-next-id todos)]
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
;; -------------------------------------------------------------------------------------
|
;; -------------------------------------------------------------------------------------
|
||||||
;; Layer 2
|
;; 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`
|
;; Layer 2 query functions, are "extractors". They take from `app-db`
|
||||||
;; and don't do any further computation on the extracted values. That further
|
;; and don't do any further computation on the extracted values. Any further
|
||||||
;; computation happens in Layer 3.
|
;; computation should happen in Layer 3.
|
||||||
;; Why? Well Layer 2 subscriptions will rerun every time that `app-db` changes.
|
;; Why? It is an efficiency thing. Every Layer 2 subscriptions will rerun anytime
|
||||||
;; So for efficiency reasons, we want them to be trivial extractors.
|
;; that `app-db` changes (in any way). As a result, we want Layer 2 to be trivial.
|
||||||
;;
|
;;
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:showing ;; usage: (subscribe [:showing])
|
:showing ;; usage: (subscribe [:showing])
|
||||||
@ -30,7 +30,7 @@
|
|||||||
;; -------------------------------------------------------------------------------------
|
;; -------------------------------------------------------------------------------------
|
||||||
;; Layer 3
|
;; 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
|
;; 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).
|
;; change. Each time it is rerun, it produces a new output (return value).
|
||||||
@ -90,8 +90,9 @@
|
|||||||
(reg-sub
|
(reg-sub
|
||||||
:visible-todos
|
:visible-todos
|
||||||
|
|
||||||
;; signal function - tells us what inputs flow into this node
|
;; Signal Function
|
||||||
;; returns a vector of two input signals
|
;; Tells us what inputs flow into this node.
|
||||||
|
;; Returns a vector of two input signals (in this case)
|
||||||
(fn [query-v _]
|
(fn [query-v _]
|
||||||
[(subscribe [:todos])
|
[(subscribe [:todos])
|
||||||
(subscribe [:showing])])
|
(subscribe [:showing])])
|
||||||
@ -107,16 +108,20 @@
|
|||||||
;; -------------------------------------------------------------------------------------
|
;; -------------------------------------------------------------------------------------
|
||||||
;; Hey, wait on!!
|
;; 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?
|
;; We only supplied one function in those registrations, not two?
|
||||||
;; Very observant of you, I'm glad you asked.
|
;; 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:
|
;; 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.
|
;; 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,
|
;; 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.
|
;; 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 ?
|
;; SUGAR ?
|
||||||
|
@ -16,27 +16,26 @@
|
|||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
|
|
||||||
;; This namespace defines the re-frame API
|
;; -- API ---------------------------------------------------------------------
|
||||||
|
|
||||||
;; When originally writing this re-frame API namespace, we used
|
|
||||||
;; this technique:
|
|
||||||
;; (def api-name (deeper.namespace/where-the-defn-is))
|
|
||||||
;;
|
;;
|
||||||
;; 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
|
;; - 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
|
;; 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
|
;; revisited the decision. So, sorry, in advance. To compensate we've
|
||||||
;; added more nudity to the official docs.
|
;; added more nudity to the official docs.
|
||||||
|
|
||||||
|
|
||||||
;; -- dispatch ---------------------------------------------------------------
|
;; -- dispatch ----------------------------------------------------------------
|
||||||
(def dispatch router/dispatch)
|
(def dispatch router/dispatch)
|
||||||
(def dispatch-sync router/dispatch-sync)
|
(def dispatch-sync router/dispatch-sync)
|
||||||
|
|
||||||
|
|
||||||
;; -- subscriptions ----------------------------------------------------------
|
;; -- subscriptions -----------------------------------------------------------
|
||||||
(def reg-sub subs/reg-sub)
|
(def reg-sub subs/reg-sub)
|
||||||
(def subscribe subs/subscribe)
|
(def subscribe subs/subscribe)
|
||||||
|
|
||||||
@ -61,7 +60,7 @@
|
|||||||
(def clear-cofx (partial registrar/clear-handlers cofx/kind)) ;; think unreg-cofx
|
(def clear-cofx (partial registrar/clear-handlers cofx/kind)) ;; think unreg-cofx
|
||||||
|
|
||||||
|
|
||||||
;; -- Events -----------------------------------------------------------------
|
;; -- Events ------------------------------------------------------------------
|
||||||
|
|
||||||
(defn reg-event-db
|
(defn reg-event-db
|
||||||
"Register the given event `handler` (function) for the given `id`. Optionally, provide
|
"Register the given event `handler` (function) for the given `id`. Optionally, provide
|
||||||
|
Loading…
x
Reference in New Issue
Block a user