如何循环遍历 re-frame 中订阅的 collection 并将数据显示为 list-item?

How do I loop through a subscribed collection in re-frame and display the data as a list-item?

考虑以下 clojurescript 代码,其中使用了 spectre、reagent 和 re-frame 框架,外部 React.js 网格组件用作视图组件。

在db.cls中:

(def default-db
  {:cats [{:id 0 :data {:text "ROOT" :test 17} :prev nil :par nil}
          {:id 1 :data {:text "Objects" :test 27} :prev nil :par 0}
          {:id 2 :data {:text "Version" :test 37} :prev nil :par 1}
          {:id 3 :data {:text "X1" :test 47} :prev nil :par 2}]})

在subs.cls

(register-sub
  :cats
  (fn [db]
    (reaction
      (select [ALL :data] (t/tree-visitor (get @db :cats))))))

来自 select 的结果:

[{:text "ROOT", :test 17} 
 {:text "Objects", :test 27} 
 {:text "Version", :test 37} 
 {:text "X1", :test 47}]

在views.cls

(defn categorymanager []
      (let [cats (re-frame/subscribe [:cats])]
         [:> Reactable.Table
             {:data (clj->js @cats)}]))

上面的代码按预期工作。

我不想用 react.js 组件显示数据,而是想遍历 :cats 向量中的每个地图并在 html ul / li 中显示 :text 项目。

我是这样开始的:

(defn categorymanager2 []
      (let [cats (re-frame/subscribe [:cats])]
         [:div
           [:ul
             (for [category @cats] 
;;--- How to continue here ?? ---
        )
        ))

预期输出:

ROOT
Objects
Version
X1

如何遍历 re-frame 中订阅的 collection 并将数据显示为 list-item? (= 标题问题)。

这是一个 ul / li 示例:

(defn phone-component
  [phone]
  [:li
   [:span (:name @phone)]
   [:p (:snippet @phone)]])

(defn phones-component
  []
  (let [phones (re-frame/subscribe [:phones])] ; subscribe to the phones value in our db
    (fn []
      [:ul (for [phone in @phones] ^{:key phone} [phone-component phone] @phones)])))

我从 this reframe tutorial 中获取了该代码。

另外,在使用 Reagent 时,map 优于 for。这是技术上的原因,只是我不知道它是什么。

编辑:有关按键和 map 的更连贯且技术上正确的解释,请参阅 的回答!


我会这样写:

(defn categorymanager2 []
  (let [cats (re-frame/subscribe [:cats])]
    (fn []
      [:div
       [:ul
        (map-indexed (fn [n cat] ;;; !!! See  !!!
                       ^{:key n}
                       [:li (:text cat)])
                     @cats)]])))
(defn main-panel []
  [:div
   [categorymanager2]])

几点:

  1. 看到 re-frame readme's Subscribe section,接近尾声,上面写着:

    subscriptions can only be used in Form-2 components and the subscription must be in the outer setup function and not in the inner render function. So the following is wrong (compare to the correct version above)…

    • 因此,您的组件是“错误的”,因为它没有将渲染器包装在内部函数中。自述文件包含所有详细信息,但简而言之,不将依赖于订阅的组件渲染器包装在内部函数中是不好的,因为这会导致组件在 db 更改时重新渲染——这不是您想要的!您希望组件仅在订阅更改时重新呈现。
  2. 编辑:说真的,请参阅 . 无论出于何种原因,我更喜欢使用 map 创建 Hiccup 标签序列。您也可以使用 for 循环,但关键是每个 [:li] Hiccup 向量在其元数据中需要一个 :key 条目,我通过使用当前类别的索引在此处添加在 @cats 向量中。如果你没有 :key,React 会在 Dev Console 中抱怨。请注意,此密钥应该以某种方式将 @cats 的此元素唯一地绑定到此标记:如果 cats 订阅更改并被打乱,结果可能不是您所期望的,因为我只是使用了这个非常简单的密钥.如果你能保证类别名称是唯一的,你可以只使用 :test 值,或 :test 值,或其他东西。重点是,键必须是唯一的,必须唯一标识这个元素。
    • (N.B.: 不要尝试使用 mapv 来制作 Hiccup 标签的 vector——re-frame 讨厌那样。必须是seq 就像 map 产生的那样。)
  3. 我还包括了一个例子 main-panel 来强调
    • 父组件不需要它们的子组件需要的订阅,而
    • 您应该使用方括号调用 categorymanager2 组件,而不是作为带有括号的函数调用(参见 Using [] instead of ())。

首先,说清楚为什么用key...

当列表非常动态时,为列表中的每个项目提供 key 非常有用 - 当定期添加和删除新列表项目时,尤其是当列表很长且项目正在更新时added/removed 靠近列表顶部。

keys 可以带来很大的性能提升,因为它们允许 React 更有效地重绘这些可变列表。或者,更准确地说,它允许 React 避免重绘与上次具有相同键、没有改变以及只是向上或向下洗牌的项目。

其次,如果列表非常静态(它不会一直改变)或者如果没有与每个项目关联的唯一值...

根本不要使用 :key。相反,像这样使用 into

(defn categorymanager []
  (let [cats (re-frame/subscribe [:cats])]
    (fn []
      [:div
       (into [:ul] (map #(vector :li (:text %)) @cats))])))

注意这里发生了什么。 map 提供的列表是折叠into [:ul] 向量。最后,看不到任何清单。只是嵌套向量。

只有在将 list 嵌入 hiccup 时才会收到有关丢失键的警告。上面没有嵌入list,只有vectors.

第三,如果你的列表真的是动态的...

为每个项目添加一个唯一的 key(在兄弟姐妹中是唯一的)。在给出的示例中,:text 本身就足够了 key(我假设它是唯一的):

(defn categorymanager []
  (let [cats (re-frame/subscribe [:cats])]
    (fn []
      [:div
        [:ul  (map #(vector :li {:key (:text %)} (:text %)) @cats)]])))

map 将产生 list,这是 [:ul] 的第一个参数。当 Reagent/React 看到 list 时,它会希望在每个项目上看到 keys(记住列表与 Reagent hiccup 中的向量不同)并且会向控制台打印警告是 keys 到失踪。

所以我们需要在list的每一项上加一个key。在上面的代码中,我们没有通过元数据添加 :key(尽管您可以根据需要这样做),而是通过第一个参数([=38 的)提供 key =]), 通常也携带样式数据。

最后 - 第 1 部分 不要按照另一个答案中的建议使用 map-indexed

key 应该是与每个项目关联的唯一值。附加一些 arb 整数没有任何用处 - 好吧,它确实消除了控制台中的警告,但如果你想要的话,你应该使用上面的 into 技术。

最后 - 第 2 部分 在这种情况下 mapfor 之间没有区别。

它们都产生 list。如果 list 有键则没有警告。但是,如果缺少密钥,则会出现很多警告。但是这个列表是如何创建的并没有涉及。

因此,此 for 版本与 map 版本几乎相同。有些人可能更喜欢它:

(defn categorymanager []
  (let [cats (re-frame/subscribe [:cats])]
    (fn []
      [:div
        [:ul  (for [i @cats] [:li {:key (:text i)} (:text i)])]])))

也可以使用这样的元数据来编写:

(defn categorymanager []
  (let [cats (re-frame/subscribe [:cats])]
    (fn []
      [:div
        [:ul  (for [i @cats] ^{:key (:text i)}[:li  (:text i)])]])))

最后 - 第 3 部分

mapv 是一个问题,因为这个问题: https://github.com/Day8/re-frame/wiki/Using-%5Bsquare-brackets%5D-instead-of-%28parentheses%29#appendix-2