为了学习Reagent、re-frame和spec,我做了一个小小的个人项目。
描述:
这是代码
(ns my.reagent-examples
(:require [reagent.core :as reagent]
[cljs.spec.alpha :as s]
[re-frame.db :as db]
[re-frame.core :as rf]))
;; -- Specs --
;; -----------
(s/def ::name string?) ; Name is a string
(s/def ::date-fin-mandat inst?) ; date is of type #inst (a timestamp)
(s/def ::role keyword?) ;role is a keyword
; One people is a map with required keys name, date and role and no optional keys
(s/def ::people (s/keys :req [::name ::date-fin-mandat ::role] ;; ?Should I use :req or :un-req ?
:opt []))
; Peoples is a vector of 0 to many people
(s/def ::peoples (s/coll-of ::people :kind vector? :min-count 0))
; A filter is a 2 elements vector of keyword like [:role :dev]
(s/def ::filter (s/tuple keyword? (s/or :s string? :k keyword?)))
; Filters is a vector of 0 to many filter
(s/def ::filters (s/coll-of ::filter :kind vector? :min-count 0))
; Spec for the whole db
(s/def ::db (s/keys :req-un [::peoples ::filters]))
;; -- Data --
;; --------
(def peoples [
{::name "julien"
::date-fin-mandat (js/Date.)
::role :dev}
{::name "juscellino"
::date-fin-mandat (js/Date. 2019 7 21)
::role :dev}
{::name "danny"
::date-fin-mandat (js/Date. 2019 4 15)
::role :dev}
{::name "nathalie"
::date-fin-mandat (js/Date. 2031 9 22)
::role :rh}
{::name "malik"
::date-fin-mandat (js/Date. 2019 1 22)
::role :analyste}
{::name "daniel"
::date-fin-mandat (js/Date. 2019 8 15)
::role :dev}])
;; -- Helpers --
;; -------------
(defn filter-people
[peoples filters]
(into
[]
(set
(flatten
(mapv
(fn [[k v]]
(filter
#(= (k %1) v)
peoples))
filters)))))
(def end-soon-style {:background-color "red"})
(def end-not-so-soon-style {:background-color "orange"})
(def end-very-not-soon-style {:background-color "green"})
(defn date+30
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 30)))
(defn date+90
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 90)))
(defn style-by-date
[date end-soon end-not-so-soon end-very-not-soon]
(let [today (js/Date.)]
(cond
(>= today date) end-soon
(< date (date+30 today)) end-not-so-soon
:else end-very-not-soon)))
;; -- 1 - Dispatch event --
;; ------------------------
; Do i need this?
(defn dispatch-add-filter-event
[new-filter]
(rf/dispatch [:add-filter [:role :dev]]))
;; -- 2 - Event Handler --
;; -----------------------
;; -- Interceptor --
;; -----------------
; Interceptor for validating spec after every add into db
(defn check-and-throw
"Throws an exception if `db` doesn't match the Spec `a-spec`."
[a-spec db]
(when-not (s/valid? a-spec db)
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
;; now we create an interceptor using `after`
(def check-spec-interceptor (rf/after (partial check-and-throw ::db)))
;; -- Define Event Handlers --
;; ---------------------------
; Initialize the app state
(rf/reg-event-db
:initialize
[check-spec-interceptor]
(fn [_ _]
{:peoples peoples
:filters [[::role :dev] [::name "julien"]]}))
; Add a new filter in app state
(rf/reg-event-db
:add-filter
[check-spec-interceptor]
(fn [db [_ new-filter]]
(assoc db :filters (conj (:filters db) new-filter))))
; Add all filters for a particular key
(rf/reg-event-db
:add-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1 values]]
(assoc db
:filters
(into
(:filters db)
(map
#(vec [key1 %1])
values)))))
; Remove a filter from app state
(rf/reg-event-db
:remove-filter
[check-spec-interceptor]
(fn [db [_ old-filter]]
(assoc db
:filters
(filterv
#(not (= %1 old-filter))
(:filters db)))))
; Remove all filters from app state for a particular key
(rf/reg-event-db
:remove-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1]]
(assoc db
:filters
(filterv
#(not (= (first %1) key1))
(:filters db)))))
;; -- 3 - Effect Handler --
;; ------------------------
;; ?? Probably nothing here??
;; -- 4 - Subscription Handler --
;; ------------------------------
; Return the vector of filters
(rf/reg-sub
:filters
(fn [db _]
(:filters db)))
; Return the peoples, unfiltered
(rf/reg-sub
:peoples
(fn [db _]
(:peoples db)))
; Return a list of filtered peoples
(rf/reg-sub
:peoples-filtered
(fn [db _]
(filter-people (:peoples db) (:filters db))))
; Given a key k, return a vector of values representing all the values present in peoples
(rf/reg-sub
:values-for-key
(fn [db [_ k]]
(map #(k %1) (:peoples db))))
; Does filter contains the value k in it?
(rf/reg-sub
:filter-contains?
(fn [db [_ k]]
(contains? (set (:filters db)) k)))
;; -- 5 - View Function --
;; -----------------------
(defn list-people
[]
[:p (pr-str @(rf/subscribe [:peoples]))])
(defn list-people-filtered
[]
[:p (pr-str @(rf/subscribe [:peoples-filtered]))])
(defn list-filter
[]
[:p (pr-str @(rf/subscribe [:filters]))])
(defn button-add-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:add-filter [::role role]]))}
(pr-str "Add filter" role)])
(defn button-remove-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:remove-filter [::role role]]))}
(pr-str "Remove filter" role)])
(defn ressource
[data]
[:tr
[:td (data ::name)]
[:td {:style (style-by-date
(data ::date-fin-mandat)
end-soon-style
end-not-so-soon-style
end-very-not-soon-style)}(str (data ::date-fin-mandat))]
[:td (data ::role)]
])
(defn checklist-filter
[key1]
(let [list-values (set @(rf/subscribe [:values-for-key key1]))]
(into
[:form
[:input
{:type "checkbox"
:id (str "all-" key1)
:name (str "all-" key1)
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-all-filters-for-key key1 list-values])
(rf/dispatch [:remove-all-filters-for-key key1])))}]
[:label {:for (str "all-" key1)} "All"]
]
(for [value list-values]
[:div
[:input
{:type "checkbox"
:id value
:name value
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-filter [key1 value]])
(rf/dispatch [:remove-filter [key1 value]])))
:checked @(rf/subscribe [:filter-contains? [key1 value]])
}]
[:label {:for value} value]]
))))
(defn peoples-ui-filtered
[]
(let [pf @(rf/subscribe [:peoples-filtered])]
(into
[:table
[:tr
[:th "NAME"]
[:th "DATE FIN MANDAT"]
[:th "ROLE"]]
[:tr
[:td [checklist-filter ::name]]
[:td ]
[:td [checklist-filter ::role]]]
]
(doall
(for [p pf]
[ressource p])))))
(defn ui
[]
[:div
[peoples-ui-filtered]
])
;; -- Kickstart the application --
;; -------------------------------
(defn ^:export run
[]
(rf/dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into ''
(js/document.getElementById "app")))
(run)您还可以查看这个要旨,它在table.cljs中有相同的代码,也可以查看klipse.html,它是一个带有Klipse设置的html页面,可以在浏览器中尝试代码。
对于评论,每一个反馈都是值得赞赏的,但特别是关于重新框架和规范.
最后,我的总体clojure/script代码如何?
我知道我应该在一个真正的项目中有很多文件(core、db、events、subs和views),但是我在Klipse的帮助下在浏览器中编码,所以我现在把所有东西都放在同一个文件中。
发布于 2019-05-09 17:36:50
不幸的是,我以前从未使用过规范、重新框架或clojurescript,但我可以注意到可以修复的一般Clojure内容。
filter-people的嵌套太多了。像您这样的代码会影响可读性(您需要自下而上地阅读它,而不是像大多数代码那样自上而下地阅读),并使其更难添加(因为您需要调整缩进并小心添加括号)。如果您使用的是->> (线程最后一个宏),那么它会更干净:
(defn filter-people
[peoples filters]
(->> filters
(mapv
(fn [[k v]]
(filter #(= (k %1) v) peoples)))
(flatten)
(set)
(vec))) ; Instead of (into [])您的date+函数有不必要的重复。我会为days添加一个参数来泛化它们:
(defn date+days [date days]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) days)))如果您真的想要一个date+90和其他类似的函数,那么用广义函数来定义它们:
(defn date+90 [date]
(date+days date 90))现在,如果需要对实现进行更改,则不需要更改多个函数。
如果我是你,我会再缩一点。你在许多地方都有一个单独的缩进空间。代码,如
(fn [db [_ old-filter]]
(assoc db
:filters
(filterv
#(not (= %1 old-filter))
(:filters db)))))比需要的更难读。我更喜欢将小型匿名函数与传递给它们的函数放在同一行,并且至少使用两个空格进行缩进。在本例中,我还可能使用一个let绑定来将其分解一些,以便更容易地处理正在发生的事情。更接近:
(fn [db [_ old-filter]]
(let [filtered (filterv #(not (= %1 old-filter))
(:filters db))]
(assoc db :filters filtered)))还请注意,(filter #(not ...是常见的,并且有一个名为remove的快捷函数。你可以在这里利用它:
(remove #(= %1 old-filter) (:filters db))虽然这会返回一个延迟序列,并且没有removev版本,所以如果您确实需要一个向量,那么您将需要添加一个对vec的调用,或者只使用您拥有的版本。
这里
(assoc db
:filters
(into
(:filters db)
(map
#(vec [key1 %1])
values)))))您正在assoc上使用db,但是新值依赖于旧值。在这种情况下,人们通常更倾向于使用update:
(fn [db [_ key1 values]]
(update db :filters
(fn [fs]
(into fs (map #(vec [key1 %1]) values)))))不幸的是,这里的收益并不大,因为您已经使用了一个短函数宏(#()),而且它们不能嵌套。不过,它经常会导致代码更干净。
我要指出的是
(contains? (set (:filters db)) k)))contains?是不必要的。可以调用集合来测试成员资格。
((set (:filters db)) k)在某些情况下(不一定要在这里;不过请参阅下面),这可能会导致代码更干净。
不过,把它放在一组,仅仅是为了测试会员资格,似乎是相当低效的。如果它已经在一套,耶,会籍测试会很快。但是,现在如何使用它,您可以对(:filters db)进行完整的迭代,这样就可以进行高效的查找。(:filters db)的查找无论如何只需要一个完整的迭代(在最坏的情况下)。我会使用some并检查是否相等:
(some #(= k %) (:filters db))或者,使用上述集作为函数提示:
(some #{k} (:filters db)) ; Use the set as a predicate这将返回第一个真结果,如果找不到结果,则返回nil (falsey)。早期退出确保它只做它需要做的工作。
https://codereview.stackexchange.com/questions/219996
复制相似问题