我该如何在 re-frame 中循环遍历已订阅的集合并将数据显示为列表项?

13
考虑以下ClojureScript代码,其中使用了Specter、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))))))

选择的结果:

[{: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中循环遍历已订阅的集合并将数据显示为列表项?(=标题问题)

3个回答

38

首先,要清楚为什么要使用key...

给列表中的每个项目提供一个key在列表非常动态时很有用——当新的列表项经常被添加和移除时,尤其是如果该列表很长,并且这些项目正在接近列表的顶部进行添加/删除。

keys可以带来巨大的性能提升,因为它们允许React更有效地重新绘制这些可变列表。或者更准确地说,它使React避免重新绘制上次与上次具有相同键值、未更改且仅向上或向下移动的项目。

其次,如果列表相对静态(不经常更改)或者每个项目没有唯一的值,请明确应该怎么做...

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

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

注意这里发生了什么。由map提供的列表被折叠到[: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(尽管您可以这样做),而是通过第一个参数([:li])提供了 key,通常还携带样式数据。

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

key 应该是与每个项目关联的唯一值。附加任意整数不会有任何有用的效果 - 好吧,它确实可以消除控制台中的警告,但如果只是想要这样做,请使用上面的 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)])]])))

最后-第三部分

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


1
在 Reddit 上,你的回答被描述为史诗般的。 :-) - nilo de roock

3

编辑:如果您需要更加连贯和技术正确的关于键和map的解释,请参考Mike Thompson的回答!


以下是我的翻译:

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

一些要点:
1.请参见 re-frame readme 的 Subscribe 部分,在末尾附近,其中写道:
订阅只能用于 Form-2 组件,并且订阅必须在外部设置函数中而不是内部呈现函数中。因此,下面是错误的(与上面的正确版本进行比较)...
因此,您的组件是“错误”的,因为它没有将渲染器包装在内部函数中。readme 中有所有细节,但简而言之,不将依赖于订阅的组件呈现器包装在内部函数中是不好的,因为这会导致组件在 db 更改时重新呈现——这不是您想要的!您希望组件仅在订阅更改时重新呈现。
2.编辑:认真看一下Mike Thompson 的答案。出于某种原因,我更喜欢使用 map 创建 Hiccup 标记的 seq。您也可以使用 for 循环,但关键点在于每个 [:li] Hiccup 向量都需要其元数据中的 :key 条目,在此处我使用当前类别在 @cats 向量中的索引添加。如果没有 :key,React 将在 Dev Console 中发出警告。请注意,此键应以某种方式唯一地将 @cats 的此元素与此标记联系起来:如果 cats 订阅更改并被混合,则结果可能不是您所期望的,因为我只使用了这个非常简单的密钥。如果可以保证类别名称是唯一的,您可以直接使用 :test 值或 :test 值或其他内容。关键是,密钥必须是唯一的,并且必须唯一地标识此元素。
(注:不要尝试使用 mapv 创建 Hiccup 标记的向量——re-frame 不支持它。必须是像 map 生成的 seq。)
3.我还包括一个示例 main-panel,以强调
  • 父组件不需要其子组件所需的订阅
  • 您应该使用方括号而不是带圆括号的函数调用 categorymanager2 组件(请参见使用 [] 而不是 ())。

这个答案非常有帮助。谢谢。 - nilo de roock

2
这是一个
    /
  • 示例:

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

    我从这个Reframe教程中获取了那段代码。
    此外,在使用Reagent时,mapfor更可取。有一个技术原因,只是我不知道是什么。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接