总纲:
- 使用组件内本地定义的试剂原子来保存整数值
-
reset! :on-change 和 :on-click 中的值
为了与re-frame的app db集成,我们需要添加以下内容:
- 在
:on-change 和:on-click 中调用re-frame/dispatch
-
re-frame/subscribe 将值存储在 re-frame 的应用程序数据库中并相应地更新本地试剂原子
让我们看一些代码,从试剂组件开始:
(defn integer-field [default-value]
(let [int-atom (atom default-value)]
(fn []
[:div
[:input {:type "text"
:value @int-atom
:on-change #(reset! int-atom (-> % .-target .-value))}]
[:button {:on-click #(adjust-int int-atom 1)} "+"]
[:button {:on-click #(adjust-int int-atom -1)}]])))
诀窍在于,每次我们收到:on-change 事件时,reset!-ing 原子并让 reagent/react 负责在幕后重新渲染场。
还要注意:on-click 调用另一个函数adjust-int 来增加/减少字段原子的值。这里是:
(defn- adjust-int [int-atom delta]
(let [v (js/parseInt @int-atom)
valid? (not (js/Number.isNaN v))
new-v (+v delta)]
(when valid?
(reset! int-atom new-v))))
按原样工作。但是,如果我们决定将整数值存储在重新帧中,我们确实需要更多代码。首先,让我们创建一个处理程序来存储它从 db 中的输入字段接收到的值:
(re-frame/register-handler
:integer-input-field-updated
(fn [db [_ value]]
(assoc db :integer-value value)))
现在,除了reset!-ing 试剂原子之外,每次输入字段的值发生变化时,我们都需要调用(re-frame/dispatch :integer-input-field-updated value)。让我们为其添加一个辅助函数:
(defn- store-int-value [int-atom value]
(reset! int-atom value)
(re-frame/dispatch :integer-input-field-updated value))
我们现在需要在每次准备好存储值时调用这个函数而不是reset!。让我们先在adjust-int 中进行更改:
(defn- adjust-int [int-atom delta]
(let [v (js/parseInt @int-atom)
valid? (not (js/Number.isNaN v))
new-v (+v delta)]
(when valid?
(store-int-value int-atom new-v)))) ;; <---- changed
让我们回顾一下到目前为止的内容:
- 输入字段和两个更改输入字段值的按钮
- 存储值并将 UI 元素联系在一起的试剂原子
- re-frame 的应用数据库中存储值的位置
我们需要一种方法来支持另一个方向的值流,从应用程序数据库到本地原子。我们可以连接一个订阅,它对(:integer-value @db) 的更改做出反应并重置我们的本地原子。等等,这听起来像是某种循环,不是吗?让我们可视化流程:
:on-click
-
(reset! int-atom) -> 重新渲染输入字段
(dispatch [:integer-input-field-updated new-value])
(assoc db :integer-value value)
- 试剂反应被触发
-
(reset! int-atom) -> 再次重新渲染输入字段
这不酷。我们需要确保信息只在一个方向流动,这意味着对:integer-value 的更改应该不触发对本地原子的更改,但对本地原子的更改会 em> 传播到:integer-value。基本上,我们的 UI 不应该对它自己发起的更新做出反应。我们可以通过在数据库中引入另一个键 re-frame/subscribe 来实现这一点,并强制所有不是来自 UI 的更新到 :integer-value 来更新两者。让我们将此键称为 :integer-value-input-field 并为其创建一个子程序和一个处理程序:
(re-frame/register-sub
:integer-field-input-value
(fn [db _]
(reaction (:integer-field-input-value @db))))
(re-frame/register-handler
:integer-value-updated ;; <--- for use by other parts of the app
(fn [db [_ value]]
(assoc db :integer-field value :integer-field-input-value value)))
最后,让我们重构 integer-field 组件:
(defn integer-field [default-value]
(let [int-atom (atom default-value)
the-int (re-frame/subscribe [:integer-field-input-value])] ;; <--- source of truth
(fn []
(reset! int-atom @the-int) ;; <--- spread the truth
[:div
[:input {:type "text"
:value @int-atom
:on-change #(store-int-value int-atom(-> % .-target .-value))}]
[:button {:on-click #(adjust-int int-atom 1)} "+"]
[:button {:on-click #(adjust-int int-atom -1)}]])))
现在,所有代码都在一个地方...
(re-frame/register-sub
:integer-field-input-value
(fn [db _]
(reaction (:integer-field-input-value @db))))
;; to be used by other parts of the app
(re-frame/register-handler
:integer-value-updated
(fn [db [_ value]]
(assoc db :integer-field value :integer-field-input-value value)))
;; to be used by the integer-field component
(re-frame/register-handler
:integer-input-field-updated
(fn [db [_ value]]
(assoc db :integer-value value)))
(defn- store-int-value [int-atom value]
(reset! int-atom value)
(re-frame/dispatch :integer-input-field-updated value))
(defn- adjust-int [int-atom delta]
(let [v (js/parseInt @int-atom)
valid? (not (js/Number.isNaN v))
new-v (+v delta)]
(when valid?
(store-int-value int-atom new-v))))
(defn integer-field [default-value]
(let [int-atom (atom default-value)
the-int (re-frame/subscribe [:integer-field-input-value])]
(fn []
(reset! int-atom @the-int)
[:div
[:input {:type "text"
:value @int-atom
:on-change #(store-int-value int-atom(-> % .-target .-value))}]
[:button {:on-click #(adjust-int int-atom 1)} "+"]
[:button {:on-click #(adjust-int int-atom -1)}]])))