July 21, 2019
By: Kevin

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]])

Tags: Reagent react clojurescript