Clojurescript、Reagent:将原子作为输入向下传递,还是用作全局变量?
Clojurescript, Reagent: pass atoms down as inputs, or use as global variables?
我正在编写一个 Clojurescript 应用程序,使用 Reagent 使我的组件具有反应性。
我有一个简单的问题。我应该
- 通过我的组件将我的原子作为输入传递,或者
- 使用原子作为全局变量,让它们'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
而不是仅仅编写组件并将它们直接连接到全局原子 (app-db
),您编写 subscriptions
只是 select/query 一些函数来自 app-db
的数据,并在 app-db
更改时将其传递给组件。
然后,组件不会直接乱用 app-db
,而是创建 events
,这些只是描述组件意图的小块数据。
这些事件被发送到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 和反应只会导致一个渲染阶段。
我正在编写一个 Clojurescript 应用程序,使用 Reagent 使我的组件具有反应性。
我有一个简单的问题。我应该
- 通过我的组件将我的原子作为输入传递,或者
- 使用原子作为全局变量,让它们'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
而不是仅仅编写组件并将它们直接连接到全局原子 (
app-db
),您编写subscriptions
只是 select/query 一些函数来自app-db
的数据,并在app-db
更改时将其传递给组件。然后,组件不会直接乱用
app-db
,而是创建events
,这些只是描述组件意图的小块数据。这些事件被发送到
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 和反应只会导致一个渲染阶段。