首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >任务结束视觉线索的员工表

任务结束视觉线索的员工表
EN

Code Review用户
提问于 2019-05-09 15:32:42
回答 1查看 49关注 0票数 2

为了学习Reagentre-framespec,我做了一个小小的个人项目。

描述:

  • 给出员工列表(姓名、任务结束日期和员工角色),并输出一个包含其信息的表。
  • 添加视觉提示,如果任务已经完成,即将完成(我选择30天)或不会很快完成。
  • 为表中的名称和角色添加筛选器(在Excel电子表格中有点像)

这是代码

代码语言:javascript
复制
(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页面,可以在浏览器中尝试代码。

对于评论,每一个反馈都是值得赞赏的,但特别是关于重新框架和规范.

  • 我是否正确理解了事件处理程序、效果处理程序等在重新框架中的分离?
  • 就像我知道我没有经常使用调度事件一样,我应该在哪里使用这些?
  • 我没有使用效果处理程序,但是如果我理解正确的话,那将是数据库查询、localStorage操作、调用外部API等的地方。在我的项目中没有用,对吗?
  • 重帧todomvc示例之后,我添加了规范和规范拦截器(从本例中粘贴的副本)。我的规格看起来正确吗?
  • 定义规范和阻断器是我在每个项目中应该做的吗?还是说我应该避免呢?

最后,我的总体clojure/script代码如何?

我知道我应该在一个真正的项目中有很多文件(coredbeventssubsviews),但是我在Klipse的帮助下在浏览器中编码,所以我现在把所有东西都放在同一个文件中。

EN

回答 1

Code Review用户

回答已采纳

发布于 2019-05-09 17:36:50

不幸的是,我以前从未使用过规范、重新框架或clojurescript,但我可以注意到可以修复的一般Clojure内容。

filter-people的嵌套太多了。像您这样的代码会影响可读性(您需要自下而上地阅读它,而不是像大多数代码那样自上而下地阅读),并使其更难添加(因为您需要调整缩进并小心添加括号)。如果您使用的是->> (线程最后一个宏),那么它会更干净:

代码语言:javascript
复制
(defn filter-people
  [peoples filters]
  (->> filters
       (mapv
         (fn [[k v]]
           (filter #(= (k %1) v) peoples)))
       (flatten)
       (set)
       (vec))) ; Instead of (into [])

您的date+函数有不必要的重复。我会为days添加一个参数来泛化它们:

代码语言:javascript
复制
(defn date+days [date days]
  (js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) days)))

如果您真的想要一个date+90和其他类似的函数,那么用广义函数来定义它们:

代码语言:javascript
复制
(defn date+90 [date]
  (date+days date 90))

现在,如果需要对实现进行更改,则不需要更改多个函数。

如果我是你,我会再缩一点。你在许多地方都有一个单独的缩进空间。代码,如

代码语言:javascript
复制
(fn [db [_ old-filter]]
  (assoc db 
         :filters 
         (filterv 
          #(not (= %1 old-filter))
          (:filters db)))))

比需要的更难读。我更喜欢将小型匿名函数与传递给它们的函数放在同一行,并且至少使用两个空格进行缩进。在本例中,我还可能使用一个let绑定来将其分解一些,以便更容易地处理正在发生的事情。更接近:

代码语言:javascript
复制
(fn [db [_ old-filter]]
  (let [filtered (filterv #(not (= %1 old-filter))
                          (:filters db))]
    (assoc db :filters filtered)))

还请注意,(filter #(not ...是常见的,并且有一个名为remove的快捷函数。你可以在这里利用它:

代码语言:javascript
复制
(remove #(= %1 old-filter) (:filters db))

虽然这会返回一个延迟序列,并且没有removev版本,所以如果您确实需要一个向量,那么您将需要添加一个对vec的调用,或者只使用您拥有的版本。

这里

代码语言:javascript
复制
(assoc db
       :filters
       (into 
        (:filters db)
        (map
         #(vec [key1 %1])
         values)))))

您正在assoc上使用db,但是新值依赖于旧值。在这种情况下,人们通常更倾向于使用update

代码语言:javascript
复制
(fn [db [_ key1 values]]
  (update db :filters
             (fn [fs]
               (into fs (map #(vec [key1 %1]) values)))))

不幸的是,这里的收益并不大,因为您已经使用了一个短函数宏(#()),而且它们不能嵌套。不过,它经常会导致代码更干净。

我要指出的是

代码语言:javascript
复制
(contains? (set (:filters db)) k)))

contains?是不必要的。可以调用集合来测试成员资格。

代码语言:javascript
复制
((set (:filters db)) k)

在某些情况下(不一定要在这里;不过请参阅下面),这可能会导致代码更干净。

不过,把它放在一组,仅仅是为了测试会员资格,似乎是相当低效的。如果它已经在一套,耶,会籍测试会很快。但是,现在如何使用它,您可以对(:filters db)进行完整的迭代,这样就可以进行高效的查找。(:filters db)的查找无论如何只需要一个完整的迭代(在最坏的情况下)。我会使用some并检查是否相等:

代码语言:javascript
复制
(some #(= k %) (:filters db))

或者,使用上述集作为函数提示:

代码语言:javascript
复制
(some #{k} (:filters db)) ; Use the set as a predicate

这将返回第一个真结果,如果找不到结果,则返回nil (falsey)。早期退出确保它只做它需要做的工作。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/219996

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档