Clojurescript,Reagent:将原子作为输入传递还是作为全局变量使用?

11

我正在编写一个Clojurescript应用程序,使用Reagent使我的组件具有响应性。

我有一个简单的问题:我应该

  1. 通过我的组件将我的atoms作为输入传递,还是
  2. 将atoms作为全局变量使用,并让它们“副作用”我的组件?

教程中,他们使用了后者,然而为了保持函数的纯净性,我选择了前者。

我是否正确地说,将它们用作全局变量(除了在定义组件输入时更少冗长)可以防止重新渲染不使用原子状态的整个父组件?

2个回答

11

如果您的组件接受原子作为参数,那么您可以使它们更加可重用和易于测试。

如果您选择将整个应用程序状态保留在单个原子中,然后使用游标将其传递给子组件,则尤其如此。

;; setup a single instance of global state
(defonce app-state
  (reagent/atom {:foo 0 :bar 0})

;; define a generic counter component that knows
;; nothing about the global state
(defn counter
  [count]
  [:div
    [:button {:onclick #(swap! count inc) "+"]
    [:span @count]])

 ;; define counter components and give them access to
 ;; specific context within the global state
 (defn app
   [state]
   [counter (reagent/cursor app-state [:foo])]
   [counter (reagent/cursor app-state [:bar])])

如果你决定使用Reagent和Re-frame,你甚至可以更进一步。Re-frame鼓励您使用特定的架构构建您的应用程序,看起来像这样。

 app-db  >  subscriptions
   ^             
handlers        v
   ^             
 events  <  components
  1. 与其直接编写组件并将其连接到全局原子(app-db),你需要编写 subscriptions,它们只是从app-db中选择/查询数据并在app-db发生更改时将其传递给组件的函数。

  2. 然后,组件不再直接操作app-db,而是创建events,这些事件只是描述组件目的的小型数据。

  3. 这些事件会发送到handlers,这些处理程序是函数,它们将event和当前的app-db作为参数,并返回一个新的app-db。然后现有的app-db被替换,触发订阅者向下传递数据到组件等等。

如果您发现自己的Reagent项目有点混乱,阅读Re-frame readme对您肯定会有所帮助,无论您是否决定使用它。


谢谢您的回答。我想知道传递原子如何使组件更容易进行测试,您能否详细说明一下? - Eric Auld

10

我更喜欢将ratom传递给组件。 Re-frame越来越流行了:https://github.com/Day8/re-frame

传递ratom并不会使您的函数更加纯净,因为原子仍可能受到副作用的影响。但是,它确实使您的组件更具灵活性和可重用性,因为它们定义其依赖项。

无论是引用全局db还是传入的db,都不会直接影响重新渲染过程。重新渲染的信号图是从向量中deref的出现构建的,它不关心ratom来自何处。但是,您可以通过创建反应来提高效率。

(defn my-component []
  (let [x (reaction (:x @db)]
    (fn []
      [:div @x]))

只有当 :x 变化时,此组件才会重新渲染(而不是当数据库中的任何内容变化时)。创建反应可能会变得繁琐,这是 re-frame 吸引人的地方之一。

(ns whip.view.reactions
  (:require [reagent.core :as reagent]
            [devcards.core :refer-macros [defcard-rg deftest]])
  (:require-macros [reagent.ratom :refer [reaction]]))
(def a (reagent/atom {:x 100 :y 200})) (def b (reaction (:x @a)))
(def c (reaction (+ @b 10)))
(defn view-c []
  (prn "Rendering view-c") [:div
  [:div @c]
  [:button {:on-click (fn [e] (swap! a update :x inc))} "inc x"]
  [:button {:on-click (fn [e] (swap! a update :y inc))} "inc y"]])
(defcard-rg reaction-example [view-c])

反应是一种非常简洁的表达数据流的方法。在这里,您从一个包含x和y值的ratom开始。然后构建一个仅观察x值的反应b。接下来,引入另一个观察b和10之和的反应c。然后创建一个以反应式方式呈现c的组件。请注意,当您单击“增加x”按钮时,视图会更新为应用表达式结果。当您单击“增加y”按钮时,不会发生任何事情。检查控制台以确认只有在单击“增加x”时才会打印“Rendering view-c”消息,这是很好的事情,因为视图不以任何方式依赖于y。如果您在视图中解除a而不是c,则即使y发生变化,它也将被重新渲染。Reagent通过requestAnimationFrame对反应和ratoms做出反应。因此,更改许多ratoms和依赖于它们的反应仅会导致一个渲染阶段。


1
有没有地方可以了解更多关于“reaction”的信息?API文档似乎有点不足,而且在介绍性教程中也没有提到!实际上,我发现这在API的许多部分都是如此。 - Boyentenbi
准确地说,我正在尝试使页面上的每个输入对除自身以外的每个输入做出反应。我认为我可以通过反应宏来实现这一点,但不确定如何做到! - Boyentenbi
https://github.com/Day8/re-frame#how-flow-happens-in-reagent <-- 这是一个很好的关于reaction的写作,其中包括了示例。 - Timothy Pratley
我已经在回答中添加了一个针对 reaction 的具体示例,希望有所帮助... 如果不清楚,请告诉我。 - Timothy Pratley
这非常有道理,谢谢。我仍然有困惑,但我认为最好开始一个新问题。 - Boyentenbi
"How Flow Happens in Reagent" 的新链接:https://day8.github.io/re-frame/flow-mechanics/ - Eric Auld

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