在Reagent中,我们假设我正在定义一个辅助函数,该函数返回父组件的渲染函数中的子组件。
这是否会在每次渲染函数运行时生成新的子组件?
这是一个最小的例子来说明:
(defn ChildComponent [text]
[:p text])
(defn ParentComponent [names-vector]
(let [renderChild (fn [i]
[ChildComponent (get names-vector i)])]
[:div
[renderChild 1]
[renderChild 3]
[renderChild 5]]))
我在renderChild
中定义了一个let
函数作为辅助函数,以避免每次使用(get names-vector i)
时重复ChildComponent
。
我希望这几乎完全等同于:
(defn ChildComponent [text]
[:p text])
(defn ParentComponent [names-vector]
[:div
[ChildComponent (get names-vector 1)]
[ChildComponent (get names-vector 3)]
[ChildComponent (get names-vector 5)]])
names-vector
中的更改可能会触发重新呈现ChildComponents,但不会触发销毁和创建。
Reagent是否将第一个示例扩展到第二个示例?或者由于重复的组件破坏/创建,第一个示例是否存在潜在的重大性能问题?
答案 0 :(得分:1)
我一直在使用Reagent大约一年了,我就是这样做的
事情。我会说这是建议做的事情。试剂也会
隐式使用参数作为指标来知道是否重新渲染
组件与否,因此它非常适合于知道何时重新渲染。现在,
我的用例可能比其他一些用户小,但我一直都是
这种设置在性能方面非常高兴。尽我所能
可以说,这是鼓励的设计模式。
要注意的一件事是处理类似的清单
元素(表格行,项目列表等),您可能想要附加元数据
唯一标识每个兄弟姐妹。 React将使用这个引擎盖进行优化
渲染。
因此,在您的情况下,您可能需要更多类似的内容:
[:div
^{:key 1} [renderChild 1]
^{:key 2} [renderChild 3]
^{:key 5} [renderChild 5]]))
<强>更新强>
所以,我猜错了:这种技术的问题是创建了一个新函数 每次重新渲染父级时,Reagent将会看到并被强制使用 调用新函数来获取潜在的新子项 - 因为“组件” 似乎已经改变了。我也没有以同样的方式使用这些功能 原始海报做了。相反,当我需要重复元素时,我选择了 形式:
(into []
(for [i [1 3 5]]
[ChildComponent (get names-vector i)]))
或者你可以改为:
(let [renderChild (fn [i]
[ChildComponent (get names-vector i)])]
(into []
(for [i [1 3 5]]
(renderChild i))))
第一种形式简单地避免了额外的功能定义。第二种形式
导致renderChild
在返回数据之前被评估,因此Reagent永远不会
看到临时功能 - 因此侧面需要使用Reagent
评估它,发现孩子们没有改变。
但是在查看我们的应用程序的代码时,我们选择了第一个表单 在所有情况下的第二个,或简单地打破功能,并给它一个 名字 - 它有意义。
此外,这个例子很差:
[:div
^{:key 1} [renderChild 1]
^{:key 2} [renderChild 3]
^{:key 5} [renderChild 5]]))
添加密钥仅在列表元素的数量会发生变化时才有意义。 AFAICT,React使用该信息来帮助优化差异计算和 允许它更容易地计算哪些成员需要被丢弃以及哪些成员被丢弃 添加。如果它是静态的,那么无所谓。一个更好的例子是:
(into [:div]
(for [val some-coll]
^{:key (compute-key val)} [:p (get some-other-coll val)]))
some-coll
的内容可能发生变化。
答案 1 :(得分:0)
我已经进一步探讨了这个问题,遗憾的是,如果组件定义发生在另一个组件的渲染函数的let
绑定中,则Reagent似乎不会缓存组件。
用于实验的示例代码:
(defonce app-state (r/atom 0))
(defn Clicker []
[:h2 {:on-click #(swap! app-state inc)} "Click me"])
(defn Displayer [n]
(let [a (r/atom (js/console.log "Component generated!"))]
(fn [n]
[:p "Hello, yes I ignore my input"])))
(defn Intermediate1 [n]
[Displayer n])
(defn Intermediate2 [n]
(let [Subcomponent (fn [x] [Displayer x])]
[Subcomponent n]))
(defn my-app []
[:div
[Clicker]
[Intermediate2 @app-state]])
(r/render
[my-app]
(js/document.getElementById "app"))
使用Intermediate1
组件,消息“Component generated!”应用程序启动时,控制台日志上会显示一次。
使用Intermediate2
,控制台日志显示“已生成组件!”每次点击Clicker
组件时。
在^{:key 1}
内添加Intermediate2
不会改变结果:
(defn Intermediate2 [n]
(let [Subcomponent (fn [x] [Displayer x])]
^{:key 1} [Subcomponent n]))
这表明每次向Displayer
发送新值时,Intermediate2
组件都会被销毁并生成,这将触发重新呈现。 (或者更确切地说,渲染新创建的组件)
因此,虽然在实践中成本可能并不高(特别是对于生成少量简单组件),但似乎let
内部渲染函数中的绑定组件会导致不必要的组件破坏/创建,可能会否定使用React样式虚拟DOM的效率优势。
答案 2 :(得分:0)
请在提问之前阅读此链接。
https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#the-three-ways
数据结构块或数据结构的函数返回块将被视为Form-1
。所有这些都将作为单一数据结构进行扩展。此范围内的任何更改都将触发重新呈现此单一数据结构。
这是否会在每次渲染函数运行时生成新的子组件?
是。由于renderChild
性质,我们会重新创建Form-1
。
我在let中定义了一个renderChild函数作为辅助函数,以避免每次使用ChildComponent时都重复(get names-vector i)。
如果您希望ChildComponent
中的任何一个只在其自己的(get names-vector i)
值更改时重新呈现,而不是在父项或兄弟项中更改时重新呈现。您应该使用Form-2
或Form-3
。 (名字 - 矢量必须是使试剂起作用的一种方法!)
(defn ChildComponent [names-vector i]
(fn [names-vector i]
[:p (get @names-vector i)]))
(defn ParentComponent [names-vector]
[:div
[ChildComponent names-vector 1]
[ChildComponent names-vector 3]
[ChildComponent names-vector 5]])