cljs代码规范和最佳实践2021-03-27版(不断更新)
(require '[reagent.core :as r])
cljs
在cljs中不要直接操作js对象
我们发布环境(release版本)的js代码和开发环境下的js代码是不一样的, release版经过了google closure的深度编译, js的变量, 函数会被混淆. 会出现本地运行ok,但是到发布环境就会出问题的情况. 在cljs代码中,尽量不要直接用interop的方式来调用和读取js对象的属性和方法.
我们cljs使用antd的组件,on-change、on-submit等很多函数中都涉及操作当前target的对象,本身是个js对象,都知道用js->clj,但是要避免直接使用类似下面的代码
:on-submit (fn [e]
((:validateFields this)
(fn [err values]
(rf/dispatch [:fabric/select-fabric-list (.-category_id values)]))))
这段代码在dev环境是没有问题的,但是category_id这个属性在生产打包时js会做混淆压缩,hash只有就没有这个属性了,这不是警告,是个bug,方式是用js->clj后用map取值:
:on-submit (fn [e]
((:validateFields this)
(fn [err values]
(rf/dispatch [:fabric/select-fabric-list
(:category_id (js->clj values))]))))
cljs小数计算
cljs环境中, ratio没有实现, bigdec不可用, 前端做高精度计算浮点数似乎也不是个好点子.
如果非要用, 使用个第三方的库吧, 比如mathjs
re-frame 和 kee-frame
- 事件处理
reg-event-db,reg-event-fx都应该使用kee-frame的版本, 避免re-frame的版本,kee-frame有更好,更统一的log. sub和event分两个namespace,两个文件定义- 事件命名不清晰时, 加上业务注释
reg-event-db和reg-event-fx的返回值
- reg-event-db的值是更新后的db
- reg-event-fx的value是nil或者更新后的coeffect.
除此之外的值都是错的. reg-event-fx返回的coeffect接下来会生成一个atom. 事件队列会修改它. 类型不匹的返回值会引发异常
只修改db的场景使用reg-event-db,而不是reg-event-fx
;; bad
(kf/reg-event-fx
:custom/change-model
(fn [{:keys [db]} [index]]
{:db (update-in db [:custom :choices]
#(merge % {:currentIndex index}))}))
;; good
(kf/reg-event-db
:custom/change-model
(fn [db [index]]
(update-in db [:custom :choices]
#(merge % {:currentIndex index}))))
Reagent 相关
reagent使用方括号来表示组件
方括号的是reagent component, 有自己的完整生命周期;
父组件刷新的时候, 子组件不一定需要刷新. 性能上有优势,而且, form2, form3必须使用方括号.
所以, 请使用方括号!!
参见下面的比较:
(defn greet
[name]
(prn "I am child" name)
[:div "Hello " name])
(def r (r/atom "round"))
(def s (r/atom "squire"))
(defn greet-family-round
[member1 member2 member3]
(prn "I am round parent")
[:div
(greet member1)
(greet member2)
(greet member3)
(greet @r)])
(defn greet-family-square
[member1 member2 member3]
(prn "I am square parent")
[:div
[greet member1]
[greet member2]
[greet member3]
[greet @s]])
(reagent.core/render-component [greet-family-round "a" "b" "c"] js/klipse-container)
(reagent.core/render-component [greet-family-square "a" "b" "c"] js/klipse-container)
此处刷新圆括号
(reset! r "well, well, well")
此处刷新方括号, 注意区别
(reset! s "well, well, well")
for生成的组件需要指定key
参见咱们的reagent深入学习第三部分
使用for的时候最好指定个每个元素的key, 要不然会出现讨厌的warning:
Warning: Each record in table should have a unique `key` prop,or set `rowKey` to an unique primary key.
;; bad
(defn list-by [entities sort-k]
[:ul
(for [[k v] (sort-by (comp sort-k val) @entities)]
[:li (:name v)])])
;; good
(defn list-by [entities sort-k]
[:ul
(for [[k v] (sort-by (comp sort-k val) @entities)]
^{:key k}
[:li (:name v)])])
;; 元素少的时候,也可以用into
(defn list-by [entities sort-k]
(into [:ul]
(for [[k v] (sort-by (comp sort-k val) @entities)]
[:li (:name v)])))
例子
(def favorites
(r/atom
{"d632" {:name "Princess torte."
:order 2}
"1ae2" {:name "Black forest gateau."
:order 3}
"5117" {:name "Apple pie."
:order 4}
"42ae" {:name "Ice cream."
:order 1}}))
(defn list-by [entities sort-k]
[:ul
(for [[k v] (sort-by (comp sort-k val) @entities)]
^{:key k}
[:li (:name v)])])
(defn favorites-by-order-and-name []
[:div
[:h3 "By order"]
[list-by favorites :order]
[:h3 "By name"]
[list-by favorites :name]])
doall
参见咱们的reagent深入学习第三部分
如果在layseq中去deref一个atom这是个bug, 一定全力避免.
Warning: Reactive deref not supported in lazy seq, it should be wrapped in doall
(def message (r/atom "一切尽在掌握!!!!"))
;; bad
(defn bad []
[:div
[:button
{:on-click
(fn [e]
(reset! message "还有没有王法!!!"))}
"Panic!"]
(for [i (range 3)]
[:h3 @message])])
;;good
(defn good []
[:div
[:button
{:on-click
(fn [e]
(reset! message "还有没有王法!!!"))}
"Panic!"]
(doall
(for [i (range 3)]
[:h3 @message]))])
不要拿atom重新生成atom
主意这儿有两个atom, 外部atom和组件内部的atom 即使内部的atom在接下来的render函数中deref, 组件也不会刷新. 因为新生成的atom只会得到上层atom的快照. 如果说在该组件生存期内外部atom的值发生变化,比如某个ajax请求后为它赋值, 则会有肉眼可见的bug发生
(defn add-questionnaire []
(let [currentTab (atom 0)
jiemoyan-questionnaire (atom @jiemoyan-q) ;; <-- 不要这么干,除非很确定jiemoyan-g 在组件生存期不变
xiaochuan-questionnaire (atom @xiaochuan-q)]
(fn []
(if (< 1 (count @jiemoyan-q))
(do
(reset! jiemoyan-questionnaire @jiemoyan-q)
(re-frame/dispatch [:clear-jiemoyan])))
(if (< 1 (count @xiaochuan-q))
(do
(reset! xiaochuan-questionnaire @xiaochuan-q)
(re-frame/dispatch [:clear-xiaochuan])))
[:div.main-body
[:div.main-content
[:> ant-tabs
{:tabs tabs
:initial-page 0
:animated true
:useOnPan true
:on-change (fn [tab, index] (reset! currentTab index))}
[:div.tabs-page-view {:style {:height "100%"}}
(if (zero? (count @jiemoyan-questionnaire)) [empty-view] [jiemoyan jiemoyan-questionnaire])]
[:div.tabs-page-view {:style {:height "100%"}}
(if (zero? (count @xiaochuan-questionnaire)) [empty-view] [xiaochuan xiaochuan-questionnaire])]]]])))