Reagent(React)深入学习第三部分
欢迎来到第三部分!
第一部分我们看到了如何生成一个reagent组件, 组件如何挂载属性, 响应事件, 在数据的驱动下的状态变化.
第二部分我们学会了为什么要以及怎么使用生命周期函数, 以及怎么避免使用Form3...
本章我们将探索如何在组件中呈现序列元素.
序列: key
先来看个例子
(require '[reagent.core :as r])
[:svg {:width 200 :height 200}
(for [i (range 30)]
[:circle
{:r (* (inc i) 5)
:cx 100
:cy 100
:fill "none"
:stroke (str "rgb(0, " (* i 10) "," (* (- 30 i) 10) ")")}])]
打开开发者工具, 到console里, 能看到:
Warning: Every element in a seq should have a unique :key: ([:circle {:r 5, :cx 100, :cy 100, :fill "none", :stroke "rgb(0, 0,300)"}] [:circle {:r 10, :cx 100, :cy 100, :fill "none", :stroke "rgb(0, 10,290)"}]...)
这是因为我们生成的结构是这样的
[:svg
[:cirle ...]
[:circle...]
[:circle...]
[:circle...]
[:circle...]
...
]
在第三部分之前, 我们都是小心的使用into函数来处理类似情况,
[:div (into
[:svg {:width 200 :height 200}]
(for [i (range 30)]
[:circle
{:r (* (inc i) 5)
:cx 100
:cy 100
:fill "none"
:stroke (str "rgb(0, " (* i 10) "," (* (- 30 i) 10) ")")}]))]
得到的结构是:
[:svg
[[:cirle ...]
[:circle...]
[:circle...]
[:circle...]
[:circle...]
...]
]
现在是直面这个问题的时候了,先让我们搞清楚react到底抱怨什么. React更新某个列表的时候, 默认是本着' 最小更新原则' , 举例来说
- 苹果
- 橘子
- 橙子
- 西瓜
- 杨梅
- 西红柿
- 黄桃
- 樱桃
如果再来一个哈密瓜, 我可以在列表的最前端增加一项 哈密瓜;
我也可以用哈密瓜替换苹果, 苹果替换橘子, 橘子替换橙子...最后插入樱桃, 总之有很多方法来实现这个更新.
又或者我有一个excel的购物清单, 上面有100个需要购买的东西, 已经拍好了顺序, 后来你又发微信给我, 说最后增加了一项, 因为只改了一项, 所以我打开列表, 把新的项目输入到原来的列表.
又或者你重新发我了一个列表, 上面有120项, 排序和以前完全不同, 而且有删除, 更有添加.
如果给每一项一个编码, react可以迅速check所需要作出的最小改变, clojrue/clojurescrit 中标记元数据的方式是^{:key k}
(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]])
对于这个key的使用原则:
- 对于有自然key的数据, 比如说从数据库取出来的来的, 带着ID的数据, 使用key是很自然的, 而且能提高render的性能
- 对于确实没有key的情况, 也不要强行造key, 直接使用into也能解决问题, 前提是数据量不大
react有的时候需要用动画来表示增删的时候, 需要有key, 这样可以标识哪个是增加的, 哪个是删除的.(例子有待补充)
lazy sequence
(def message (r/atom "一切尽在掌握! ! ! ! "))
(defn bad []
[:div
[:button
{:on-click
(fn [e]
(reset! message "还有没有王法! ! ! "))}
"Panic!"]
(for [i (range 3)]
[:h3 @message])])
控制台有个warning:
Warning: Reactive deref not supported in lazy seq, it should be wrapped in doall
从Reageng的视角来看, 执行bad函数的时候,并没有deref message. 因为[:h3 @message]执行的时候也不知道他的parent是 bad.
所以我们需要一个函数能够让[:h3 @message]今早执行, doall,vec,into都可以. (for [x @xs] ...)这种情况是不需要去
doall的, 而且这种情况比起刚才的例子要普遍的多.
性能测试
(require '[clojure.string :as string])
(def words
["ice" "cream" "chocolate" "pastry" "pudding" "raspberry" "mousse"
"vanilla" "wafer" "waffle" "cake" "torte" "gateau" "pie" "cookie"
"cupcake" "mini" "hot" "caramel" "meringue" "lemon" "marzipan" "mocha"
"strawberry" "tart" "custard" "fruit" "baklava" "jelly" "banana" "coconut"])
(defn rand-name []
(string/capitalize (string/join " " (take (+ 2 (rand-int 5)) (shuffle words)))))
(def desserts (r/atom ()))
(defn make-a-dessert [e]
(swap! desserts conj {:id (random-uuid)
:name (rand-name)}))
(defn make-many-desserts [e]
(dotimes [i 100]
(make-a-dessert nil)))
(defn color-for [x]
(str "#" (.toString (bit-and (hash x) 0xFFFFFF) 16)))
(defn dessert-item [{:keys [id name]}]
[:li
[:svg {:width 50 :height 50}
[:circle
{:r 20 :cx 25 :cy 25 :fill (color-for id)}]
[:rect {:x 15 :y 15 :width 20 :height 20 :fill (color-for name)}]]
[:span [:em [:strong name]]]])
(defn desserts-list []
[:ol
(for [dessert @desserts]
^{:key (:id dessert)}
[dessert-item dessert])])
(defn dessertinator []
[:div
[:button {:on-click make-a-dessert} "Invent a new dessert"]
[:button {:on-click make-many-desserts} "Invent 100 new desserts"]
[desserts-list]])