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

Clojurescript, Reagent: pass atoms down as inputs, or use as global variables?

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

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

  1. 通过我的组件将我的原子作为输入传递,或者
  2. 使用原子作为全局变量,让它们'side-affect'我的组件?

tutorial 中他们使用后一种选择,但是为了保持我的功能纯净,我选择了前者。

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

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

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

;; 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 只是 select/query 一些函数来自 app-db 的数据,并在 app-db 更改时将其传递给组件。

  2. 然后,组件不会直接乱用 app-db,而是创建 events,这些只是描述组件意图的小块数据。

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

如果您发现您的 Reagent 项目有点混乱并且 Re-frame readme 是一本很棒的读物,无论您是否决定使用它,这绝对是有帮助的。

我更喜欢将 ratom 传递给组件。 重新框架正变得流行 https://github.com/Day8/re-frame

传入一个原子并不能使你的函数更纯粹,原子仍然可以产生副作用。它确实使您的组件更加灵活和可重用,因为它们定义了它们的依赖关系。

无论是引用全局数据库还是传入数据库,都不会直接影响重新渲染。何时渲染的信号图是根据向量内部 deref 的出现构建的,它不关心 ratom 来自哪里。但是,您可以通过创建反应来提高效率。

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

该组件只会在 :x 发生变化时重新渲染(不会在 db 发生任何变化时重新渲染。创建反应可能会变得乏味,这是 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 的 ratom 开始 和 y 值。然后,您构建一个只观察 x 值的反应 b。接下来,引入另一个观察 b 和 10 之和的反应 c。然后创建一个反应性地渲染 c 的组件。请注意,当您单击“inc x”按钮时,视图会更新为应用表达式的结果。当您单击“inc y”按钮时,没有任何反应。检查控制台以确认“Rendering view-c”消息仅在单击“inc x”时打印。这是一件非常好的事情,因为视图不以任何方式依赖于 y。如果您要在视图中取消引用 a 而不是 c,即使 y 发生更改,它也会重新呈现。 试剂通过 requestAnimationFrame 对 reactions 和 ratoms 做出反应。因此,更改依赖于它们的许多 ratoms 和反应只会导致一个渲染阶段。